/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { isIdentifierChar, isIdentifierStart, isKeywordFunc, isNewLine, reservedWords } from './identifier';
import { getOptions, ParserOptions } from './options';
import { getLineInfo, Position, SourceLocation } from './location';
import { Comment, Label, ParserError, RegConfig } from './global_type';
import { lineBreak, lineBreakG, nonASCIIwhitespace } from './whitespace';

const INT_VAL_P_2 = 2;
const INT_VAL_P_3 = 3;
const INT_VAL_P_4 = 4;
const INT_VAL_P_5 = 5;
const INT_VAL_P_6 = 6;
const INT_VAL_P_7 = 7;
const INT_VAL_P_8 = 8;
const INT_VAL_P_10 = 10;
const INT_VAL_P_13 = 13;
const INT_VAL_P_14 = 14;
const INT_VAL_P_16 = 16;
const INT_VAL_P_32 = 32;
const INT_VAL_P_33 = 33;
const INT_VAL_P_34 = 34;
const INT_VAL_P_36 = 36;
const INT_VAL_P_37 = 37;
const INT_VAL_P_38 = 38;
const INT_VAL_P_39 = 39;
const INT_VAL_P_40 = 40;
const INT_VAL_P_41 = 41;
const INT_VAL_P_42 = 42;
const INT_VAL_P_43 = 43;
const INT_VAL_P_44 = 44;
const INT_VAL_P_45 = 45;
const INT_VAL_P_46 = 46;
const INT_VAL_P_47 = 47;
const INT_VAL_P_48 = 48;
const INT_VAL_P_49 = 49;
const INT_VAL_P_50 = 50;
const INT_VAL_P_51 = 51;
const INT_VAL_P_52 = 52;
const INT_VAL_P_53 = 53;
const INT_VAL_P_54 = 54;
const INT_VAL_P_55 = 55;
const INT_VAL_P_56 = 56;
const INT_VAL_P_57 = 57;
const INT_VAL_P_58 = 58;
const INT_VAL_P_59 = 59;
const INT_VAL_P_60 = 60;
const INT_VAL_P_61 = 61;
const INT_VAL_P_62 = 62;
const INT_VAL_P_63 = 63;
const INT_VAL_P_64 = 64;
const INT_VAL_P_65 = 65;
const INT_VAL_P_66 = 66;
const INT_VAL_P_69 = 69;
const INT_VAL_P_79 = 79;
const INT_VAL_P_87 = 87;
const INT_VAL_P_88 = 88;
const INT_VAL_P_91 = 91;
const INT_VAL_P_92 = 92;
const INT_VAL_P_93 = 93;
const INT_VAL_P_94 = 94;
const INT_VAL_P_96 = 96;
const INT_VAL_P_97 = 97;
const INT_VAL_P_98 = 98;
const INT_VAL_P_101 = 101;
const INT_VAL_P_102 = 102;
const INT_VAL_P_110 = 110;
const INT_VAL_P_111 = 111;
const INT_VAL_P_114 = 114;
const INT_VAL_P_116 = 116;
const INT_VAL_P_117 = 117;
const INT_VAL_P_118 = 118;
const INT_VAL_P_120 = 120;
const INT_VAL_P_123 = 123;
const INT_VAL_P_124 = 124;
const INT_VAL_P_125 = 125;
const INT_VAL_P_126 = 126;
const INT_VAL_P_160 = 160;
const INT_VAL_P_255 = 255;
const INT_VAL_P_5760 = 5760;
const INT_VAL_P_8232 = 8232;
const INT_VAL_P_8233 = 8233;

const INT_VAL_P_0X10000 = 0x10000;
const INT_VAL_P_0XD800 = 0xd800;
const INT_VAL_P_0XDC00 = 0xdc00;
const INT_VAL_P_0XFFFF = 0xffff;
const INT_VAL_P_0X10FFFF = 0x10ffff;
let INT_VAL_P_0XD7FF = 0xd7ff;
let INT_VAL_P_0XE000 = 0xe000;
let INT_VAL_P_0X35FDC00 = 0x35fdc00;
const INT_VAL_P_1023 = 1023;

function last<T>(stack: T[]): T {
  return stack[stack.length - 1];
}

let empty: null[] = new Array();

let beforeExpr = true;
let startsExpr = true;
let isLoop = true;
let isAssign = true;
let prefixBool = true;
let postfixBool = true;

let keywords = new Map<string, TokenType>();

class TokenOptions {
  keyword?: string;
  beforeExpr?: boolean;
  startsExpr?: boolean;
  rightAssociative?: boolean;
  isLoop?: boolean;
  isAssign?: boolean;
  prefix?: boolean;
  postfix?: boolean;
  binop?: number;
}

class TokenType {
  label: string;
  keyword: string | null = null;
  beforeExpr: boolean;
  startsExpr: boolean;
  rightAssociative: boolean;
  isLoop: boolean;
  isAssign: boolean;
  prefix: boolean;
  postfix: boolean;
  binop?: number;
  updateContext?: (target: Tokenizer, prevType?: TokenType) => void;

  constructor(label: string, conf: TokenOptions = {}) {
    this.label = label;
    this.keyword = conf.keyword ?? null;
    this.beforeExpr = conf.beforeExpr ?? false;
    this.startsExpr = conf.startsExpr ?? false;
    this.rightAssociative = conf.rightAssociative ?? false;
    this.isLoop = conf.isLoop ?? false;
    this.isAssign = conf.isAssign ?? false;
    this.prefix = conf.prefix ?? false;
    this.postfix = conf.postfix ?? false;
    this.binop = conf.binop !== null && conf.binop !== undefined ? conf.binop : undefined;
    this.updateContext = undefined;
  }
}

function createKeyword(name: string, options: TokenOptions = {}): TokenType {
  options.keyword = name;
  let token = new TokenType(name, options);
  keywords.set(name, token);
  return token;
}

function createBinop(name: string, binop: number): TokenType {
  let conf = new TokenOptions();
  conf.binop = binop;
  return new TokenType(name, conf);
}

class TypesCls {
  num: TokenType;
  regexp: TokenType;
  string: TokenType;
  name: TokenType;
  eof: TokenType;
  bracketL: TokenType;
  bracketR: TokenType;
  braceL: TokenType;
  braceBarL: TokenType;
  braceR: TokenType;
  braceBarR: TokenType;
  parenL: TokenType;
  parenR: TokenType;
  comma: TokenType;
  semi: TokenType;
  colon: TokenType;
  doubleColon: TokenType;
  dot: TokenType;
  question: TokenType;
  arrow: TokenType;
  template: TokenType;
  ellipsis: TokenType;
  backQuote: TokenType;
  dollarBraceL: TokenType;
  at: TokenType;
  eq: TokenType;
  assign: TokenType;
  incDec: TokenType;
  prefix: TokenType;
  logicalOR: TokenType;
  logicalAND: TokenType;
  bitwiseOR: TokenType;
  bitwiseXOR: TokenType;
  bitwiseAND: TokenType;
  equality: TokenType;
  relational: TokenType;
  bitShift: TokenType;
  plusMin: TokenType;
  modulo: TokenType;
  star: TokenType;
  slash: TokenType;
  exponent: TokenType;
  _break: TokenType;
  _case: TokenType;
  _catch: TokenType;
  _continue: TokenType;
  _debugger: TokenType;
  _default: TokenType;
  _do: TokenType;
  _else: TokenType;
  _finally: TokenType;
  _for: TokenType;
  _function: TokenType;
  _if: TokenType;
  _return: TokenType;
  _switch: TokenType;
  _throw: TokenType;
  _try: TokenType;
  _let: TokenType;
  _var: TokenType;
  _const: TokenType;
  _while: TokenType;
  _with: TokenType;
  _new: TokenType;
  _this: TokenType;
  _super: TokenType;
  _class: TokenType;
  _extends: TokenType;
  _export: TokenType;
  _import: TokenType;
  _null: TokenType;
  _true: TokenType;
  _false: TokenType;
  _in: TokenType;
  _instanceof: TokenType;
  _typeof: TokenType;
  _void: TokenType;
  _delete: TokenType;
  _yield: TokenType;
  jsxTagStart: TokenType;
  jsxText: TokenType;
  jsxName: TokenType;
  jsxTagEnd: TokenType;

  constructor() {
    let startsOp = new TokenOptions();
    startsOp.startsExpr = startsExpr;

    let beforeOP = new TokenOptions();
    beforeOP.beforeExpr = beforeExpr;

    let allOp = new TokenOptions();
    allOp.beforeExpr = beforeExpr;
    allOp.startsExpr = startsExpr;

    let opIncDec = new TokenOptions();
    opIncDec.prefix = prefixBool;
    opIncDec.postfix = postfixBool;
    opIncDec.startsExpr = startsExpr;

    let opBeforeIsAssign = new TokenOptions();
    opBeforeIsAssign.beforeExpr = beforeExpr;
    opBeforeIsAssign.isAssign = isAssign;

    let opPlusMin = new TokenOptions();
    opPlusMin.beforeExpr = beforeExpr;
    opPlusMin.binop = 9;
    opPlusMin.prefix = prefixBool;
    opPlusMin.startsExpr = startsExpr;

    let opPerfix = new TokenOptions();
    opPerfix.beforeExpr = beforeExpr;
    opPerfix.prefix = prefixBool;
    opPerfix.startsExpr = startsExpr;

    let opExponent = new TokenOptions();
    opExponent.beforeExpr = beforeExpr;
    opExponent.binop = 11;
    opExponent.rightAssociative = true;

    let loopAndBefore = new TokenOptions();
    loopAndBefore.isLoop = isLoop;
    loopAndBefore.beforeExpr = beforeExpr;

    let loop = new TokenOptions();
    loop.isLoop = isLoop;

    let _inOP = new TokenOptions();
    _inOP.beforeExpr = beforeExpr;
    _inOP.binop = 7;

    this.num = new TokenType('num', startsOp);
    this.regexp = new TokenType('regexp', startsOp);
    this.string = new TokenType('string', startsOp);
    this.name = new TokenType('name', startsOp);
    this.eof = new TokenType('eof');
    this.bracketL = new TokenType('[', allOp);
    this.bracketR = new TokenType(']');
    this.braceL = new TokenType('{', allOp);
    this.braceBarL = new TokenType('{|', allOp);
    this.braceR = new TokenType('}');
    this.braceBarR = new TokenType('|}');
    this.parenL = new TokenType('(', allOp);
    this.parenR = new TokenType(')');
    this.comma = new TokenType(',', beforeOP);
    this.semi = new TokenType(';', beforeOP);
    this.colon = new TokenType(':', beforeOP);
    this.doubleColon = new TokenType('::', beforeOP);
    this.dot = new TokenType('.');
    this.question = new TokenType('?', beforeOP);
    this.arrow = new TokenType('=>', beforeOP);
    this.template = new TokenType('template');
    this.ellipsis = new TokenType('...', beforeOP);
    this.backQuote = new TokenType('`', startsOp);
    this.dollarBraceL = new TokenType('${', allOp);
    this.at = new TokenType('@');
    this.eq = new TokenType('=', opBeforeIsAssign);
    this.assign = new TokenType('_=', opBeforeIsAssign);
    this.incDec = new TokenType('++/--', opIncDec);
    this.prefix = new TokenType('prefix', opPerfix);
    this.logicalOR = createBinop('||', 1);
    this.logicalAND = createBinop('&&', INT_VAL_P_2);
    this.bitwiseOR = createBinop('|', INT_VAL_P_3);
    this.bitwiseXOR = createBinop('^', INT_VAL_P_4);
    this.bitwiseAND = createBinop('&', INT_VAL_P_5);
    this.equality = createBinop('==/!=', INT_VAL_P_6);
    this.relational = createBinop('</>', INT_VAL_P_7);
    this.bitShift = createBinop('<</>>', INT_VAL_P_8);
    this.plusMin = new TokenType('+/-', opPlusMin);
    this.modulo = createBinop('%', INT_VAL_P_10);
    this.star = createBinop('*', INT_VAL_P_10);
    this.slash = createBinop('/', INT_VAL_P_10);
    this.exponent = new TokenType('**', opExponent);
    this._break = createKeyword('break');
    this._case = createKeyword('case', beforeOP);
    this._catch = createKeyword('catch');
    this._continue = createKeyword('continue');
    this._debugger = createKeyword('debugger');
    this._default = createKeyword('default', beforeOP);
    this._do = createKeyword('do', loopAndBefore);
    this._else = createKeyword('else', beforeOP);
    this._finally = createKeyword('finally');
    this._for = createKeyword('for', loop);
    this._function = createKeyword('function', startsOp);
    this._if = createKeyword('if');
    this._return = createKeyword('return', beforeOP);
    this._switch = createKeyword('switch');
    this._throw = createKeyword('throw', opPerfix);
    this._try = createKeyword('try');
    this._var = createKeyword('var');
    this._let = createKeyword('let');
    this._const = createKeyword('const');
    this._while = createKeyword('while', loop);
    this._with = createKeyword('with');
    this._new = createKeyword('new', allOp);
    this._this = createKeyword('this', startsOp);
    this._super = createKeyword('super', startsOp);
    this._class = createKeyword('class', startsOp);
    this._extends = createKeyword('extends', beforeOP);
    this._export = createKeyword('export');
    this._import = createKeyword('import', startsOp);
    this._null = createKeyword('null', startsOp);
    this._true = createKeyword('true', startsOp);
    this._false = createKeyword('false', startsOp);
    this._in = createKeyword('in', _inOP);
    this._instanceof = createKeyword('instanceof', _inOP);
    this._typeof = createKeyword('typeof', opPerfix);
    this._void = createKeyword('void', opPerfix);
    this._delete = createKeyword('delete', opPerfix);
    this._yield = createKeyword('yield', allOp);
    this.jsxName = new TokenType('jsxName');
    this.jsxText = new TokenType('jsxText', beforeOP);
    this.jsxTagStart = new TokenType('jsxTagStart', startsOp);
    this.jsxTagEnd = new TokenType('jsxTagEnd');
    this.parenR.updateContext = (target: Tokenizer, prev?: TokenType): void => {
      let selfLess = target;
      if (selfLess.state.context!.length === 1) {
        selfLess.state.exprAllowed = true;
      }

      let out = selfLess.state.context!.pop();
      if (out === tc.braceStatement && selfLess.curContext() === tc.functionExpression) {
        selfLess.state.context!.pop();
        selfLess.state.exprAllowed = false;
      } else if (out === tc.templateQuasi) {
        selfLess.state.exprAllowed = true;
      } else {
        selfLess.state.exprAllowed = !out!.isExpr;
      }
    };

    this.braceR.updateContext = this.parenR.updateContext;
    this.name.updateContext = (target: Tokenizer, prevType?: TokenType): void => {
      let selfLess = target;
      selfLess.state.exprAllowed = false;

      if (prevType === ttIns._let || prevType === ttIns._const || prevType === ttIns._var) {
        let str = selfLess.input.slice(selfLess.state.end);
        if (lineBreak.test(str)) {
          selfLess.state.exprAllowed = true;
        }
      }
    };

    this.braceL.updateContext = (target: Tokenizer, prevType?: TokenType): void => {
      let selfLess = target;
      let tokContext = selfLess.braceIsBlock(prevType!) ? tc.braceStatement : tc.braceExpression;
      selfLess.state.context!.push(tokContext);
      selfLess.state.exprAllowed = true;
    };

    this.dollarBraceL.updateContext = (target: Tokenizer): void => {
      let selfLess = target;
      selfLess.state.context!.push(tc.templateQuasi);
      selfLess.state.exprAllowed = true;
    };

    this.parenL.updateContext = (target: Tokenizer, prevType?: TokenType): void => {
      let selfLess = target;
      let statementParens = prevType === ttIns._if || prevType === ttIns._for || prevType === ttIns._with || prevType === ttIns._while;
      selfLess.state.context!.push(statementParens ? tc.parenStatement : tc.parenExpression);
      selfLess.state.exprAllowed = true;
    };

    this.incDec.updateContext = (): void => {};

    this._function.updateContext = (target: Tokenizer, prevType?: TokenType): void => {
      let selfLess = target;
      if (selfLess.curContext() !== tc.braceStatement) {
        selfLess.state.context!.push(tc.functionExpression);
      }
      selfLess.state.exprAllowed = false;
    };

    this.backQuote.updateContext = (target: Tokenizer, prevType?: TokenType): void => {
      let selfLess = target;
      if (selfLess.curContext() === tc.template) {
        selfLess.state.context!.pop();
      } else {
        selfLess.state.context!.push(tc.template);
      }
      selfLess.state.exprAllowed = false;
    };
    this.jsxTagStart.updateContext = (target: Tokenizer, prevType?: TokenType): void => {
      let selfLess = target;
      selfLess.state.context!.push(tc.jExpr); // treat as beginning of JSX expression
      selfLess.state.context!.push(tc.joTag); // start opening tag context
      selfLess.state.exprAllowed = false;
    };

    this.jsxTagEnd.updateContext = (target: Tokenizer, prevType?: TokenType): void => {
      let selfLess = target;
      let out = selfLess.state.context!.pop();
      if ((out === tc.joTag && prevType === ttIns.slash) || out === tc.jcTag) {
        selfLess.state.context!.pop();
        selfLess.state.exprAllowed = selfLess.curContext() === tc.jExpr;
      } else {
        selfLess.state.exprAllowed = true;
      }
    };
  }
}

let ttIns = new TypesCls();

class State {
  strict: boolean = false;

  input: string | null = null;

  potentialArrowAt: number | null = null;

  inFunction: boolean = false;

  inGenerator: boolean = false;

  inMethod: string | boolean | null = null;

  inAsync: boolean = false;

  inType: boolean = false;

  inPropertyName: boolean = false;

  noAnonFunctionType: boolean = false;

  labels: (Node | Label)[] | null = null;

  decorators: Node[] | null = null;

  tokens: (Token | Comment)[] | null = null;

  comments: Comment[] | null = null;

  trailingComments: Comment[] | null = null;

  leadingComments: Comment[] | null = null;

  commentStack: Node[] | null = null;

  curLine: number = 0;

  startLoc: Position | null = null;

  endLoc: Position | null = null;

  pos: number = 0;

  lineStart: number = 0;

  type: TokenType | null = null;

  value: string | RegConfig | number | boolean | null = null;

  start: number = 0;

  end: number = 0;

  lastTokEndLoc: Position | null = null;

  lastTokStartLoc: Position | null = null;

  lastTokStart: number = 0;

  lastTokEnd: number = 0;

  context: TokContext[] | null = null;

  exprAllowed: boolean = false;

  containsEsc: boolean = false;

  containsOctal: boolean = false;

  octalPosition: number | null = null;

  invalidTemplateEscapePosition: number | null = null;

  exportedIdentifiers: string[] | null = null;

  commentPreviousNode: Node | null = null;

  init(options: ParserOptions, input: string): State {
    this.strict = options.strictMode === false ? false : options.sourceType === 'module';

    this.input = input;

    this.potentialArrowAt = -1;

    this.inMethod = false;
    this.inFunction = false;
    this.inGenerator = false;
    this.inAsync = false;
    this.inPropertyName = false;
    this.inType = false;
    this.noAnonFunctionType = false;

    this.labels = new Array();

    this.decorators = new Array();

    this.tokens = new Array();

    this.comments = new Array();

    this.trailingComments = new Array();
    this.leadingComments = new Array();
    this.commentStack = new Array();

    this.pos = 0;
    this.lineStart = 0;
    this.curLine = options.startLine;

    this.type = ttIns.eof;
    this.value = null;
    this.start = this.pos;
    this.end = this.pos;

    this.startLoc = this.curPosition();
    this.endLoc = this.curPosition();

    this.lastTokEndLoc = null;
    this.lastTokStartLoc = null;
    this.lastTokStart = this.pos;
    this.lastTokEnd = this.pos;

    this.context = [tc.braceStatement];
    this.exprAllowed = true;

    this.containsEsc = this.containsOctal = false;
    this.octalPosition = null;

    this.invalidTemplateEscapePosition = null;

    this.exportedIdentifiers = new Array();

    return this;
  }

  curPosition(): Position {
    return new Position(this.curLine, this.pos - this.lineStart);
  }

  clone(skipArrays: boolean | null): State {
    let state = new State();
    return state;
  }
}

class Token {
  type: TokenType;

  value: string | RegConfig | number | boolean;

  start: number;

  end: number;

  loc: SourceLocation;

  constructor(state: State) {
    this.type = state.type!;
    this.value = state.value!;
    this.start = state.start;
    this.end = state.end;
    this.loc = new SourceLocation(state.startLoc!, state.endLoc!);
  }
}

function codePointToString(code: number): string {
  if (code <= INT_VAL_P_0XFFFF) {
    return String.fromCharCode(code);
  } else {
    return String.fromCharCode(((code - INT_VAL_P_0X10000) >> INT_VAL_P_10) + INT_VAL_P_0XD800,
      ((code - INT_VAL_P_0X10000) & INT_VAL_P_1023) + INT_VAL_P_0XDC00);
  }
}

class Tokenizer {
  state: State;

  options: ParserOptions;

  isLookahead: boolean = false;

  input: string;

  inModule: boolean = false;

  constructor(options: ParserOptions, input: string) {
    this.options = options;
    this.state = new State();
    this.input = input;
    this.state.init(options, input);
  }

  next(): void {
    if (!this.isLookahead) {
      this.state.tokens!.push(new Token(this.state));
    }
    this.state.lastTokEnd = this.state.end;
    this.state.lastTokStart = this.state.start;
    this.state.lastTokEndLoc = this.state.endLoc;
    this.state.lastTokStartLoc = this.state.startLoc;
    this.nextToken();
  }

  eat(type: TokenType): boolean {
    if (this.match(type)) {
      this.next();
      return true;
    } else {
      return false;
    }
  }

  match(type: TokenType): boolean {
    return this.state.type === type;
  }

  isKeyword(word: string): boolean {
    return isKeywordFunc(word);
  }

  lookahead(): State {
    let old = this.state;
    this.state = old.clone(true);
    this.isLookahead = true;
    this.next();
    this.isLookahead = false;
    let curr = this.state.clone(true);
    this.state = old;
    return curr;
  }

  setStrict(strict: boolean): void {
    this.state.strict = strict;
    if (!this.match(ttIns.num) && !this.match(ttIns.string)) {
      return;
    }
    this.state.pos = this.state.start;
    while (this.state.pos < this.state.lineStart) {
      this.state.lineStart = this.input.lastIndexOf('\n', this.state.lineStart - INT_VAL_P_2) + 1;
      this.state.curLine -= 1;
    }
    this.nextToken();
  }

  curContext(): TokContext {
    return this.state.context![this.state.context!.length - 1];
  }

  nextToken(): void {
    let curContext = this.curContext();
    if (curContext === null || curContext === undefined || !curContext.preserveSpace) {
      this.skipSpace();
    }
    this.state.containsOctal = false;
    this.state.octalPosition = null;
    this.state.start = this.state.pos;
    this.state.startLoc = this.state.curPosition();
    if (this.state.pos >= this.input.length) {
      return this.finishToken(ttIns.eof, null);
    }
    if (curContext.overridee) {
      return curContext.overridee(this);
    } else {
      return this.readToken(this.fullCharCodeAtPos());
    }
  }

  readToken(code: number): void {
    if (isIdentifierStart(code) || code === INT_VAL_P_92) {
      return this.readWord();
    } else {
      return this.getTokenFromCode(code);
    }
  }

  fullCharCodeAtPos(): number {
    let code = this.input.charCodeAt(this.state.pos);
    if (code <= INT_VAL_P_0XD7FF || code >= INT_VAL_P_0XE000) {
      return code;
    }
    let next = this.input.charCodeAt(this.state.pos + 1);
    return (code << INT_VAL_P_10) + next - INT_VAL_P_0X35FDC00;
  }

  pushComment(block: boolean, text: string, start: number, end: number, startLoc: Position, endLoc: Position): void {
    let comment = new Comment(block ? 'CommentBlock' : 'CommentLine', text, start, end, new SourceLocation(startLoc, endLoc));

    if (!this.isLookahead) {
      this.state.tokens!.push(comment);
      this.state.comments!.push(comment);
      this.addComment(comment);
    }
  }

  addComment(comment: Comment): void {
    throw Error('impletement in subclass parser');
  }

  raise(pos: number, message: string): void {
    throw Error('impletement in subclass parser');
  }

  hasPlugin(plugin: string): boolean {
    throw Error('impletement in subclass parser');
    return false;
  }

  unexpected(pos: number | null, messageOrType: TokenType | string = 'Unexpected token'): null {
    throw Error('impletement in subclass parser');
    return null;
  }

  skipBlockComment(): void {
    let startLoc = this.state.curPosition();
    let start = this.state.pos;
    this.state.pos += INT_VAL_P_2;
    let end = this.input.indexOf('*/', this.state.pos);
    if (end === -1) {
      this.raise(this.state.pos - INT_VAL_P_2, 'Unterminated comment');
    }
    this.state.pos = end + INT_VAL_P_2;
    lineBreakG.lastIndex = start;
    let match: RegExpExecArray | null;
    while ((match = lineBreakG.exec(this.input)) && match.index < this.state.pos) {
      this.state.curLine += 1;
      this.state.lineStart = match.index + match[0].length;
    }
    this.pushComment(true, this.input.slice(start + INT_VAL_P_2, end), start, this.state.pos, startLoc, this.state.curPosition());
  }

  skipLineComment(startSkip: number): void {
    let start = this.state.pos;
    let startLoc = this.state.curPosition();
    this.state.pos += startSkip;
    let ch = this.input.charCodeAt(this.state.pos);
    while (this.state.pos < this.input.length && ch !== INT_VAL_P_10 && ch !== INT_VAL_P_13 && ch !== INT_VAL_P_8232 && ch !== INT_VAL_P_8233) {
      this.state.pos += 1;
      ch = this.input.charCodeAt(this.state.pos);
    }
    this.pushComment(false, this.input.slice(start + startSkip, this.state.pos), start, this.state.pos, startLoc, this.state.curPosition());
  }

  skipSpace(): void {
    let bkWhile = false;
    while (this.state.pos < this.input.length) {
      let ch = this.input.charCodeAt(this.state.pos);
      switch (ch) {
        case INT_VAL_P_32:
        case INT_VAL_P_160: // ' '
          this.state.pos += 1;
          break;
        case INT_VAL_P_13:
          if (this.input.charCodeAt(this.state.pos + 1) === INT_VAL_P_10) {
            this.state.pos += 1;
          }
        case INT_VAL_P_10:
        case INT_VAL_P_8232:
        case INT_VAL_P_8233:
          this.state.pos += 1;
          this.state.curLine += 1;
          this.state.lineStart = this.state.pos;
          break;
        case INT_VAL_P_47: // '/'
          switch (this.input.charCodeAt(this.state.pos + 1)) {
            case INT_VAL_P_42: // '*'
              this.skipBlockComment();
              break;
            case INT_VAL_P_47:
              this.skipLineComment(INT_VAL_P_2);
              break;
            default:
              bkWhile = true;
              break;
          }
          break;
        default:
          if ((ch > INT_VAL_P_8 && ch < INT_VAL_P_14) || (ch >= INT_VAL_P_5760 && nonASCIIwhitespace.test(String.fromCharCode(ch)))) {
            this.state.pos += 1;
          } else {
            bkWhile = true;
            break;
          }
      }
      if (bkWhile) {
        break;
      }
    }
  }

  finishToken(type: TokenType, val: string | RegConfig | number | null): void {
    this.state.end = this.state.pos;
    this.state.endLoc = this.state.curPosition();
    let prevType = this.state.type!;
    this.state.type = type;
    this.state.value = val;
    this.updateContext(prevType);
  }

  readTokenDot(): void {
    let next = this.input.charCodeAt(this.state.pos + 1);
    if (next >= INT_VAL_P_48 && next <= INT_VAL_P_57) {
      this.readNumber(true);
    }

    let next2 = this.input.charCodeAt(this.state.pos + INT_VAL_P_2);
    if (next === INT_VAL_P_46 && next2 === INT_VAL_P_46) {
      this.state.pos += INT_VAL_P_3;
      this.finishToken(ttIns.ellipsis, null);
    } else {
      this.state.pos += 1;
      this.finishToken(ttIns.dot, null);
    }
  }

  readTokenSlash(): void {
    if (this.state.exprAllowed) {
      this.state.pos += 1;
      return this.readRegexp();
    }

    let next = this.input.charCodeAt(this.state.pos + 1);
    if (next === INT_VAL_P_61) {
      return this.finishOp(ttIns.assign, INT_VAL_P_2);
    } else {
      return this.finishOp(ttIns.slash, 1);
    }
  }

  readTokenMultModulo(code: number): void {
    // '%*'
    let type = code === INT_VAL_P_42 ? ttIns.star : ttIns.modulo;
    let width = 1;
    let next = this.input.charCodeAt(this.state.pos + 1);

    if (next === INT_VAL_P_42) {
      // '*'
      width++;
      next = this.input.charCodeAt(this.state.pos + INT_VAL_P_2);
      type = ttIns.exponent;
    }

    if (next === INT_VAL_P_61) {
      width++;
      type = ttIns.assign;
    }

    return this.finishOp(type, width);
  }

  readTokenPipeAmp(code: number): void {
    let next = this.input.charCodeAt(this.state.pos + 1);
    if (next === code) {
      return this.finishOp(code === INT_VAL_P_124 ? ttIns.logicalOR : ttIns.logicalAND, INT_VAL_P_2);
    }
    if (next === INT_VAL_P_61) {
      return this.finishOp(ttIns.assign, INT_VAL_P_2);
    }
    if (code === INT_VAL_P_124 && next === INT_VAL_P_125 && this.hasPlugin('flow')) {
      return this.finishOp(ttIns.braceBarR, INT_VAL_P_2);
    }
    return this.finishOp(code === INT_VAL_P_124 ? ttIns.bitwiseOR : ttIns.bitwiseAND, 1);
  }

  readTokenCaret(): void {
    // '^'
    let next = this.input.charCodeAt(this.state.pos + 1);
    if (next === INT_VAL_P_61) {
      return this.finishOp(ttIns.assign, INT_VAL_P_2);
    } else {
      return this.finishOp(ttIns.bitwiseXOR, 1);
    }
  }

  readTokenPlusMin(code: number): void {
    // '+-'
    let next = this.input.charCodeAt(this.state.pos + 1);

    if (next === code) {
      if (
        next === INT_VAL_P_45 &&
          this.input.charCodeAt(this.state.pos + INT_VAL_P_2) === INT_VAL_P_62 &&
        lineBreak.test(this.input.slice(this.state.lastTokEnd, this.state.pos))
      ) {
        this.skipLineComment(INT_VAL_P_3);
        this.skipSpace();
        return this.nextToken();
      }
      return this.finishOp(ttIns.incDec, INT_VAL_P_2);
    }

    if (next === INT_VAL_P_61) {
      return this.finishOp(ttIns.assign, INT_VAL_P_2);
    } else {
      return this.finishOp(ttIns.plusMin, 1);
    }
  }

  readTokenLtGt(code: number): void {
    // '<>'
    let next = this.input.charCodeAt(this.state.pos + 1);
    let size = 1;

    if (next === code) {
      size = code === INT_VAL_P_62 && this.input.charCodeAt(this.state.pos + INT_VAL_P_2) === INT_VAL_P_62 ? INT_VAL_P_3 : INT_VAL_P_2;
      if (this.input.charCodeAt(this.state.pos + size) === INT_VAL_P_61) {
        return this.finishOp(ttIns.assign, size + 1);
      }
      return this.finishOp(ttIns.bitShift, size);
    }

    if (
      next === INT_VAL_P_33 &&
        code === INT_VAL_P_60 &&
        this.input.charCodeAt(this.state.pos + INT_VAL_P_2) === INT_VAL_P_45 &&
        this.input.charCodeAt(this.state.pos + INT_VAL_P_3) === INT_VAL_P_45
    ) {
      if (this.inModule) {
        this.unexpected(null);
      }
      this.skipLineComment(INT_VAL_P_4);
      this.skipSpace();
      return this.nextToken();
    }
    if (next === INT_VAL_P_61) {
      size = INT_VAL_P_2;
    }
    return this.finishOp(ttIns.relational, size);
  }

  readTokenEqExcl(code: number): void {
    let next = this.input.charCodeAt(this.state.pos + 1);
    if (next === INT_VAL_P_61) {
      return this.finishOp(ttIns.equality, this.input.charCodeAt(this.state.pos + INT_VAL_P_2) === INT_VAL_P_61 ? INT_VAL_P_3 : INT_VAL_P_2);
    }
    if (code === INT_VAL_P_61 && next === INT_VAL_P_62) {
      this.state.pos += INT_VAL_P_2;
      return this.finishToken(ttIns.arrow, null);
    }
    return this.finishOp(code === INT_VAL_P_61 ? ttIns.eq : ttIns.prefix, 1);
  }

  getTokenFromCode(code: number): void {
    switch (code) {
      case INT_VAL_P_46:
        return this.readTokenDot();
      case INT_VAL_P_40:
        this.state.pos += 1;
        return this.finishToken(ttIns.parenL, null);
      case INT_VAL_P_41:
        this.state.pos += 1;
        return this.finishToken(ttIns.parenR, null);
      case INT_VAL_P_59:
        this.state.pos += 1;
        return this.finishToken(ttIns.semi, null);
      case INT_VAL_P_44:
        this.state.pos += 1;
        return this.finishToken(ttIns.comma, null);
      case INT_VAL_P_91:
        this.state.pos += 1;
        return this.finishToken(ttIns.bracketL, null);
      case INT_VAL_P_93:
        this.state.pos += 1;
        return this.finishToken(ttIns.bracketR, null);
      case INT_VAL_P_123:
        if (this.hasPlugin('flow') && this.input.charCodeAt(this.state.pos + 1) === INT_VAL_P_124) {
          return this.finishOp(ttIns.braceBarL, INT_VAL_P_2);
        } else {
          this.state.pos += 1;
          return this.finishToken(ttIns.braceL, null);
        }
      case INT_VAL_P_125:
        this.state.pos += 1;
        return this.finishToken(ttIns.braceR, null);
      case INT_VAL_P_58:
        if (this.hasPlugin('functionBind') && this.input.charCodeAt(this.state.pos + 1) === INT_VAL_P_58) {
          return this.finishOp(ttIns.doubleColon, INT_VAL_P_2);
        } else {
          this.state.pos += 1;
          return this.finishToken(ttIns.colon, null);
        }
      case INT_VAL_P_63:
        this.state.pos += 1;
        return this.finishToken(ttIns.question, null);
      case INT_VAL_P_64:
        this.state.pos += 1;
        return this.finishToken(ttIns.at, null);
      case INT_VAL_P_96:
        this.state.pos += 1;
        return this.finishToken(ttIns.backQuote, null);
      case INT_VAL_P_48:
        let next = this.input.charCodeAt(this.state.pos + 1);
        if (next === INT_VAL_P_120 || next === INT_VAL_P_88) {
          return this.readRadixNumber(INT_VAL_P_16);
        }
        if (next === INT_VAL_P_111 || next === INT_VAL_P_79) {
          return this.readRadixNumber(INT_VAL_P_8);
        }
        if (next === INT_VAL_P_98 || next === INT_VAL_P_66) {
          return this.readRadixNumber(INT_VAL_P_2);
        }
      case INT_VAL_P_49:
      case INT_VAL_P_50:
      case INT_VAL_P_51:
      case INT_VAL_P_52:
      case INT_VAL_P_53:
      case INT_VAL_P_54:
      case INT_VAL_P_55:
      case INT_VAL_P_56:
      case INT_VAL_P_57:
        return this.readNumber(false);
      case INT_VAL_P_34:
      case INT_VAL_P_39:
        return this.readString(code);
      case INT_VAL_P_47:
        return this.readTokenSlash();
      case INT_VAL_P_37:
      case INT_VAL_P_42:
        return this.readTokenMultModulo(code);
      case INT_VAL_P_124:
      case INT_VAL_P_38:
        return this.readTokenPipeAmp(code);
      case INT_VAL_P_94:
        return this.readTokenCaret();
      case INT_VAL_P_43:
      case INT_VAL_P_45:
        return this.readTokenPlusMin(code);
      case INT_VAL_P_60:
      case INT_VAL_P_62:
        return this.readTokenLtGt(code);
      case INT_VAL_P_61:
      case INT_VAL_P_33:
        return this.readTokenEqExcl(code);
      case INT_VAL_P_126:
        return this.finishOp(ttIns.prefix, 1);
      default:
        break;
    }
    return this.raise(this.state.pos, `Unexpected character '${codePointToString(code)}'`);
  }

  finishOp(type: TokenType, size: number): void {
    let str = this.input.slice(this.state.pos, this.state.pos + size);
    this.state.pos += size;
    this.finishToken(type, str);
  }

  readRegexp(): void {
    let start = this.state.pos;
    let escaped = false;
    let inClass = false;
    while (true) {
      if (this.state.pos >= this.input.length) {
        this.raise(start, 'Unterminated regular expression');
      }
      let ch = this.input.charAt(this.state.pos);
      if (lineBreak.test(ch)) {
        this.raise(start, 'Unterminated regular expression');
      }
      if (escaped) {
        escaped = false;
      } else {
        if (ch === '[') {
          inClass = true;
        } else if (ch === ']' && inClass) {
          inClass = false;
        } else if (ch === '/' && !inClass) {
          break;
        }
        escaped = ch === '\\';
      }
      this.state.pos += 1;
    }
    let content = this.input.slice(start, this.state.pos);
    this.state.pos += 1;
    let mods = this.readWord1();
    if (mods) {
      let validFlags = new RegExp('^[gmsiyu]*$');
      if (!validFlags.test(mods)) {
        this.raise(start, 'Invalid regular expression flag');
      }
    }
    this.finishToken(ttIns.regexp, new RegConfig(content, mods));
  }
  readInt(radix: number, len?: number): number | null {
    let start = this.state.pos;
    let total = 0;
    let i = 0;
    let e = len == null ? Number.MAX_SAFE_INTEGER : len;
    while (i < e) {
      let code = this.input.charCodeAt(this.state.pos);
      let val: number;
      if (code >= INT_VAL_P_97) {
        val = code - INT_VAL_P_97 + INT_VAL_P_10;
      } else if (code >= INT_VAL_P_65) {
        val = code - INT_VAL_P_65 + INT_VAL_P_10;
      } else if (code >= INT_VAL_P_48 && code <= INT_VAL_P_57) {
        val = code - INT_VAL_P_48;
      } else {
        val = Number.MAX_SAFE_INTEGER;
      }
      if (val >= radix) {
        break;
      }
      this.state.pos += 1;
      total = total * radix + val;
      i += 1;
    }
    if (this.state.pos === start || (len != null && this.state.pos - start !== len)) {
      return null;
    }
    return total;
  }

  readRadixNumber(radix: number): void {
    this.state.pos += INT_VAL_P_2;

    let val = this.readInt(radix);
    if (val == null) {
      this.raise(this.state.start + INT_VAL_P_2, 'Expected number in radix ' + radix);
    }
    if (isIdentifierStart(this.fullCharCodeAtPos())) {
      this.raise(this.state.pos, 'Identifier directly after number');
    }
    this.finishToken(ttIns.num, val);
  }

  readNumber(startsWithDot: boolean): void {
    let start = this.state.pos;
    let octal = this.input.charCodeAt(this.state.pos) === INT_VAL_P_48;
    let isFloat = false;

    if (!startsWithDot && this.readInt(INT_VAL_P_10) === null) {
      this.raise(start, 'Invalid number');
    }
    let next = this.input.charCodeAt(this.state.pos);
    if (next === INT_VAL_P_46) {
      // '.'
      this.state.pos += 1;
      this.readInt(INT_VAL_P_10);
      isFloat = true;
      next = this.input.charCodeAt(this.state.pos);
    }
    if (next === INT_VAL_P_69 || next === INT_VAL_P_101) {
      // 'eE'
      this.state.pos += 1;
      next = this.input.charCodeAt(this.state.pos);
      if (next === INT_VAL_P_43 || next === INT_VAL_P_45) {
        this.state.pos += 1; // '+-'
      }
      if (this.readInt(INT_VAL_P_10) === null) {
        this.raise(start, 'Invalid number');
      }
      isFloat = true;
    }

    if (isIdentifierStart(this.fullCharCodeAtPos())) {
      this.raise(this.state.pos, 'Identifier directly after number');
    }
    let str = this.input.slice(start, this.state.pos);
    let val: number | null;
    if (isFloat) {
      val = Number(str);
    } else if (!octal || str.length === 1) {
      val = parseInt1(str, INT_VAL_P_10);
    } else if (new RegExp('[89]').test(str) || this.state.strict) {
      this.raise(start, 'Invalid number');
    } else {
      val = parseInt1(str, INT_VAL_P_8);
    }
    this.finishToken(ttIns.num, val!);
  }

  readCodePoint(throwOnInvalid: boolean): number | null {
    let ch = this.input.charCodeAt(this.state.pos);
    let code: number | null = null;

    if (ch === INT_VAL_P_123) {
      // '{'
      this.state.pos += 1;
      let codePos = this.state.pos;
      code = this.readHexChar(this.input.indexOf('}', this.state.pos) - this.state.pos, throwOnInvalid);
      this.state.pos += 1;
      if (code === null) {
        this.state.invalidTemplateEscapePosition = this.state.invalidTemplateEscapePosition! - 1; // to point to the '\'' instead of the 'u'
      } else if (code > INT_VAL_P_0X10FFFF) {
        if (throwOnInvalid) {
          this.raise(codePos, 'Code point out of bounds');
        } else {
          this.state.invalidTemplateEscapePosition = codePos - INT_VAL_P_2;
          return null;
        }
      }
    } else {
      code = this.readHexChar(INT_VAL_P_4, throwOnInvalid);
    }
    return code;
  }

  readString(quote: number): void {
    let outStr = '';
    this.state.pos += 1;
    let chunkStart = this.state.pos;
    while (true) {
      if (this.state.pos >= this.input.length) {
        this.raise(this.state.start, 'Unterminated string constant');
      }
      let ch = this.input.charCodeAt(this.state.pos);
      if (ch === quote) {
        break;
      }
      if (ch === INT_VAL_P_92) {
        outStr += this.input.slice(chunkStart, this.state.pos);
        outStr += this.readEscapedChar(false);
        chunkStart = this.state.pos;
      } else {
        if (isNewLine(ch)) {
          this.raise(this.state.start, 'Unterminated string constant');
        }
        this.state.pos += 1;
      }
    }
    outStr += this.input.slice(chunkStart, this.state.pos);
    this.state.pos += 1;
    this.finishToken(ttIns.string, outStr);
  }

  readTmplToken(): void {
    let outStr = '';
    let chunkStart = this.state.pos;
    let containsInvalid = false;
    while (true) {
      if (this.state.pos >= this.input.length) {
        this.raise(this.state.start, 'Unterminated template');
      }
      let ch = this.input.charCodeAt(this.state.pos);

      if (ch === INT_VAL_P_96 || (ch === INT_VAL_P_36 && this.input.charCodeAt(this.state.pos + 1) === INT_VAL_P_123)) {
        // '`', '${'
        if (this.state.pos === this.state.start && this.match(ttIns.template)) {
          if (ch === INT_VAL_P_36) {
            this.state.pos += INT_VAL_P_2;
            this.finishToken(ttIns.dollarBraceL, null);
            return;
          } else {
            this.state.pos += 1;
            this.finishToken(ttIns.backQuote, null);
            return;
          }
        }
        outStr += this.input.slice(chunkStart, this.state.pos);
        this.finishToken(ttIns.template, containsInvalid ? null : outStr);
        return;
      }
      if (ch === INT_VAL_P_92) {
        // '\'
        outStr += this.input.slice(chunkStart, this.state.pos);
        let escaped = this.readEscapedChar(true);
        if (escaped === null) {
          containsInvalid = true;
        } else {
          outStr += escaped;
        }
        chunkStart = this.state.pos;
      } else if (isNewLine(ch)) {
        outStr += this.input.slice(chunkStart, this.state.pos);
        this.state.pos += 1;
        switch (ch) {
          case INT_VAL_P_13:
            if (this.input.charCodeAt(this.state.pos) === INT_VAL_P_10) {
              this.state.pos += 1;
            }
          case INT_VAL_P_10:
            outStr += '\n';
            break;
          default:
            outStr += String.fromCharCode(ch);
            break;
        }
        this.state.curLine += 1;
        this.state.lineStart = this.state.pos;
        chunkStart = this.state.pos;
      } else {
        this.state.pos += 1;
      }
    }
  }

  readEscapedChar(inTemplate: boolean): string | null {
    let throwOnInvalid = !inTemplate;
    this.state.pos += 1;
    let ch = this.input.charCodeAt(this.state.pos);
    this.state.pos += 1;
    switch (ch) {
      case INT_VAL_P_110:
        return '\n';
      case INT_VAL_P_114:
        return '\r';
      case INT_VAL_P_120: {
        let code = this.readHexChar(INT_VAL_P_2, throwOnInvalid);
        return code === null ? null : String.fromCharCode(code);
      }
      case INT_VAL_P_117: {
        let code = this.readCodePoint(throwOnInvalid);
        return code === null ? null : codePointToString(code);
      }
      case INT_VAL_P_116:
        return '\t';
      case INT_VAL_P_98:
        return '\b';
      case INT_VAL_P_118:
        return '\u000b';
      case INT_VAL_P_102:
        return '\f';
      case INT_VAL_P_13:
        if (this.input.charCodeAt(this.state.pos) === INT_VAL_P_10) {
          this.state.pos += 1;
        }
      case INT_VAL_P_10:
        this.state.lineStart = this.state.pos;
        this.state.curLine += 1;
        return '';
      default:
        if (ch >= INT_VAL_P_48 && ch <= INT_VAL_P_55) {
          let codePos = this.state.pos - 1;
          let octalStr = this.input!.substr(this.state.pos! - 1, INT_VAL_P_3).match(new RegExp('^[0-7]+'))![0];
          let octal = parseInt1(octalStr, INT_VAL_P_8);
          if (octal > INT_VAL_P_255) {
            octalStr = octalStr.slice(0, -1);
            octal = parseInt1(octalStr, INT_VAL_P_8);
          }
          if (octal > 0) {
            if (inTemplate) {
              this.state.invalidTemplateEscapePosition = codePos;
              return null;
            } else if (this.state.strict) {
              this.raise(codePos, 'Octal literal in strict mode');
            } else if (!this.state.containsOctal) {
              this.state.containsOctal = true;
              this.state.octalPosition = codePos;
            }
          }
          this.state.pos += octalStr.length - 1;
          return String.fromCharCode(octal);
        }
        return String.fromCharCode(ch);
    }
  }

  readHexChar(len: number, throwOnInvalid: boolean): number | null {
    let codePos = this.state.pos;
    let n = this.readInt(INT_VAL_P_16, len);
    if (n === null) {
      if (throwOnInvalid) {
        this.raise(codePos, 'Bad character escape sequence');
      } else {
        this.state.pos = codePos - 1;
        this.state.invalidTemplateEscapePosition = codePos - 1;
      }
    }
    return n;
  }

  readWord1(): string {
    this.state.containsEsc = false;
    let word = '';
    let first = true;
    let chunkStart = this.state.pos;
    while (this.state.pos < this.input.length) {
      let ch = this.fullCharCodeAtPos();
      if (isIdentifierChar(ch)) {
        this.state.pos += ch <= INT_VAL_P_0XFFFF ? 1 : INT_VAL_P_2;
      } else if (ch === INT_VAL_P_92) {
        // "\"
        this.state.containsEsc = true;

        word += this.input.slice(chunkStart, this.state.pos);
        let escStart = this.state.pos;
        this.state.pos += 1;
        if (this.input.charCodeAt(this.state.pos) !== INT_VAL_P_117) {
          // "u"
          this.raise(this.state.pos, 'Expecting Unicode escape sequence \\uXXXX');
        }

        this.state.pos += 1;
        let esc = this.readCodePoint(true);
        if (!(first ? isIdentifierStart : isIdentifierChar)(esc!)) {
          this.raise(escStart, 'Invalid Unicode escape');
        }
        word += codePointToString(esc!);
        chunkStart = this.state.pos;
      } else {
        break;
      }
      first = false;
    }
    return word + this.input.slice(chunkStart, this.state.pos);
  }

  readWord(): void {
    let word = this.readWord1();
    let type = ttIns.name;
    // console.log('--------------word', word);
    // console.log('**************', this.state.start);
    if (!this.state.containsEsc && this.isKeyword(word)) {
      type = keywords.get(word) as TokenType;
    }
    return this.finishToken(type, word);
  }

  braceIsBlock(prevType: TokenType): boolean {
    if (prevType === ttIns.colon) {
      let parent = this.curContext();
      if (parent === tc.braceStatement || parent === tc.braceExpression) {
        return !parent.isExpr;
      }
    }

    if (prevType === ttIns._return) {
      return lineBreak.test(this.input.slice(this.state.lastTokEnd, this.state.start));
    }

    if (prevType === ttIns._else || prevType === ttIns.semi || prevType === ttIns.eof || prevType === ttIns.parenR) {
      return true;
    }

    if (prevType === ttIns.braceL) {
      return this.curContext() === tc.braceStatement;
    }

    return !this.state.exprAllowed;
  }

  updateContext(prevType: TokenType): void {
    let type = this.state.type!;
    let update: (target: Tokenizer, prevType?: TokenType) => void;

    if (type.keyword && prevType === ttIns.dot) {
      this.state.exprAllowed = false;
    } else if ((update = type.updateContext!)) {
      update(this, prevType);
    } else {
      this.state.exprAllowed = type.beforeExpr;
    }
  }
}

class TokContext {
  token: string;

  isExpr: boolean;

  preserveSpace: boolean;

  overridee?: (p: Tokenizer) => void;

  constructor(token: string, isExpr?: boolean, preserveSpace?: boolean, overridee?: (tok: Tokenizer) => void) {
    this.token = token;
    this.isExpr = isExpr === undefined ? false : true;
    let preserveSpaceBool = false;
    if (preserveSpace !== undefined) {
      preserveSpaceBool = preserveSpace === undefined ? false : true;
    }
    this.preserveSpace = preserveSpaceBool;
    this.overridee = overridee;
  }
}

class TokContextCls {
  braceStatement = new TokContext('{', false);

  braceExpression = new TokContext('{', true);

  templateQuasi = new TokContext('${', true);

  parenStatement = new TokContext('(', false);

  parenExpression = new TokContext('(', true);

  template = new TokContext('`', true, true, (p: Tokenizer) => {
    p.readTmplToken();
  });

  functionExpression = new TokContext('function', true);

  joTag = new TokContext('<tag', false);

  jcTag = new TokContext('</tag', false);

  jExpr = new TokContext('<tag>...</tag>', true, true);
}

let tc = new TokContextCls();

class ExpPos {
  start: number;

  constructor(start: number) {
    this.start = start;
  }
}

class ParamsObj {
  params: Node[];

  rest: Node | null = null;

  constructor(params: Node[], rest: Node | null) {
    this.params = params;
    this.rest = rest;
  }
}

function getQualifiedJSXName(object: Node): string | null {
  if (object.type === 'JSXIdentifier') {
    return object.name as string;
  }

  if (object.type === 'JSXNamespacedName') {
    return object.namespace!.name! + ':' + ((object.name! as Node).name as string);
  }

  if (object.type === 'JSXMemberExpression') {
    return getQualifiedJSXName(object.object!)! + '.' + getQualifiedJSXName(object.property!)!;
  }

  return null;
}

let HEX_NUMBER = new RegExp('^[da-fA-F]+$');
let DECIMAL_NUMBER = new RegExp('^d+$');

enum XHTMLEntities {
  QUOT = '\u0022',
  AMP = '&',
  APOS = '\u0027',
  LT = '<',
  GT = '>',
  NBSP = '\u00A0',
  IEXCL = '\u00A1',
  CENT = '\u00A2',
  POUND = '\u00A3',
  CURREN = '\u00A4',
  YEN = '\u00A5',
  BRVBAR = '\u00A6',
  SECT = '\u00A7',
  UML = '\u00A8',
  COPY = '\u00A9',
  ORDF = '\u00AA',
  LAQUO = '\u00AB',
  NOT = '\u00AC',
  SHY = '\u00AD',
  REG = '\u00AE',
  MACR = '\u00AF',
  DEG = '\u00B0',
  PLUSMN = '\u00B1',
  SUP2 = '\u00B2',
  SUP3 = '\u00B3',
  ACUTE = '\u00B4',
  MICRO = '\u00B5',
  PARA = '\u00B6',
  MIDDOT = '\u00B7',
  CEDIL = '\u00B8',
  SUP1 = '\u00B9',
  ORDM = '\u00BA',
  RAQUO = '\u00BB',
  FRAC14 = '\u00BC',
  FRAC12 = '\u00BD',
  FRAC34 = '\u00BE',
  IQUEST = '\u00BF',
  AGRAVE = '\u00C0',
  AACUTE = '\u00C1',
  ACIRC = '\u00C2',
  ATILDE = '\u00C3',
  AUML = '\u00C4',
  ARING = '\u00C5',
  AELIG = '\u00C6',
  CCEDIL = '\u00C7',
  EGRAVE = '\u00C8',
  EACUTE = '\u00C9',
  ECIRC = '\u00CA',
  EUML = '\u00CB',
  IGRAVE = '\u00CC',
  IACUTE = '\u00CD',
  ICIRC = '\u00CE',
  IUML = '\u00CF',
  ETH = '\u00D0',
  NTILDE = '\u00D1',
  OGRAVE = '\u00D2',
  OACUTE = '\u00D3',
  OCIRC = '\u00D4',
  OTILDE = '\u00D5',
  OUML = '\u00D6',
  TIMES = '\u00D7',
  OSLASH = '\u00D8',
  UGRAVE = '\u00D9',
  UACUTE = '\u00DA',
  UCIRC = '\u00DB',
  UUML = '\u00DC',
  YACUTE = '\u00DD',
  THORN = '\u00DE',
  SZLIG = '\u00DF',
  AGRAVEF = '\u00E0',
  AACUTEF = '\u00E1',
  ACIRCF = '\u00E2',
  ATILDEF = '\u00E3',
  AUMLF = '\u00E4',
  ARINGF = '\u00E5',
  AELIGF = '\u00E6',
  CCEDILF = '\u00E7',
  EGRAVEF = '\u00E8',
  EACUTEF = '\u00E9',
  ECIRCF = '\u00EA',
  EUMLF = '\u00EB',
  IGRAVEF = '\u00EC',
  IACUTEF = '\u00ED',
  ICIRCF = '\u00EE',
  IUMLF = '\u00EF',
  ETHF = '\u00F0',
  NTILDEF = '\u00F1',
  OGRAVEF = '\u00F2',
  OACUTEF = '\u00F3',
  OCIRCF = '\u00F4',
  OTILDEF = '\u00F5',
  OUMLF = '\u00F6',
  DIVIDE = '\u00F7',
  OSLASHF = '\u00F8',
  UGRAVEF = '\u00F9',
  UACUTEF = '\u00FA',
  UCIRCF = '\u00FB',
  UUMLF = '\u00FC',
  YACUTEF = '\u00FD',
  THORNF = '\u00FE',
  YUMLF = '\u00FF',
  OELIG = '\u0152',
  OELIGF = '\u0153',
  SCARON = '\u0160',
  SCARONF = '\u0161',
  YUML = '\u0178',
  FNOF = '\u0192',
  CIRC = '\u02C6',
  TILDE = '\u02DC',
  ALPHA = '\u0391',
  BETA = '\u0392',
  GAMMA = '\u0393',
  DELTA = '\u0394',
  EPSILON = '\u0395',
  ZETA = '\u0396',
  ETA = '\u0397',
  THETA = '\u0398',
  IOTA = '\u0399',
  KAPPA = '\u039A',
  LAMBDA = '\u039B',
  MU = '\u039C',
  NU = '\u039D',
  XI = '\u039E',
  OMICRON = '\u039F',
  PI = '\u03A0',
  RHO = '\u03A1',
  SIGMA = '\u03A3',
  TAU = '\u03A4',
  UPSILON = '\u03A5',
  PHI = '\u03A6',
  CHI = '\u03A7',
  PSI = '\u03A8',
  OMEGA = '\u03A9',
  ALPHAF = '\u03B1',
  BETEF = '\u03B2',
  GAMMAF = '\u03B3',
  DELTAF = '\u03B4',
  EPSILONF = '\u03B5',
  ZETAF = '\u03B6',
  ETAF = '\u03B7',
  THETAF = '\u03B8',
  IOTAF = '\u03B9',
  KAPPAF = '\u03BA',
  LAMBDAF = '\u03BB',
  MUF = '\u03BC',
  UNF = '\u03BD',
  XIF = '\u03BE',
  OMICRONF = '\u03BF',
  PIF = '\u03C0',
  RHOF = '\u03C1',
  SIGMAF = '\u03C2',
  SIFMA = '\u03C3',
  TAUF = '\u03C4',
  UPSILONF = '\u03C5',
  PHIF = '\u03C6',
  CHIF = '\u03C7',
  PSIF = '\u03C8',
  OMEGAF = '\u03C9',
  THETASYMF = '\u03D1',
  UPSIHF = '\u03D2',
  PIV = '\u03D6',
  ENSP = '\u2002',
  EMSP = '\u2003',
  THINSP = '\u2009',
  ZWNJ = '\u200C',
  ZWJ = '\u200D',
  LRM = '\u200E',
  RLM = '\u200F',
  NDASH = '\u2013',
  MDASH = '\u2014',
  LSQUO = '\u2018',
  RSQUO = '\u2019',
  SNQUO = '\u201A',
  LSQUOF = '\u201C',
  RDQUOF = '\u201D',
  BDQUOF = '\u201E',
  DAGGER = '\u2020',
  DAGGERF = '\u2021',
  BULL = '\u2022',
  HELLIP = '\u2026',
  PREMIL = '\u2030',
  PRIME = '\u2032',
  PRIMEF = '\u2033',
  LSAQUO = '\u2039',
  RSAQUO = '\u203A',
  OLINE = '\u203E',
  FRASL = '\u2044',
  EURO = '\u20AC',
  IMAGE = '\u2111',
  WEIERP = '\u2118',
  REAL = '\u211C',
  TRADE = '\u2122',
  ALEFSYM = '\u2135',
  LARRF = '\u2190',
  UARRF = '\u2191',
  RARRF = '\u2192',
  DARRF = '\u2193',
  HARRF = '\u2194',
  CRARR = '\u21B5',
  LARR = '\u21D0',
  UARR = '\u21D1',
  RARR = '\u21D2',
  DARR = '\u21D3',
  HARR = '\u21D4',
  FORALL = '\u2200',
  PART = '\u2202',
  EXIST = '\u2203',
  EMPTY = '\u2205',
  NABLA = '\u2207',
  ISIN = '\u2208',
  NOTIN = '\u2209',
  NI = '\u220B',
  PROD = '\u220F',
  SUM = '\u2211',
  MINUS = '\u2212',
  LOWAST = '\u2217',
  RADIC = '\u221A',
  PROP = '\u221D',
  INFIN = '\u221E',
  ANG = '\u2220',
  AND = '\u2227',
  OR = '\u2228',
  CAP = '\u2229',
  CUP = '\u222A',
  INT = '\u222B',
  THERE4 = '\u2234',
  SIM = '\u223C',
  CONG = '\u2245',
  ASYMP = '\u2248',
  NE = '\u2260',
  EQUIV = '\u2261',
  LE = '\u2264',
  GE = '\u2265',
  SUB = '\u2282',
  SUP = '\u2283',
  NSUB = '\u2284',
  SUBE = '\u2286',
  SUPE = '\u2287',
  OPLUS = '\u2295',
  OTIMES = '\u2297',
  PERP = '\u22A5',
  SDOT = '\u22C5',
  LCEIL = '\u2308',
  RCEIL = '\u2309',
  LFLOOR = '\u230A',
  RFLOOR = '\u230B',
  LANG = '\u2329',
  RANG = '\u232A',
  LOZ = '\u25CA',
  SPADES = '\u2660',
  CLUBS = '\u2663',
  HEARTS = '\u2665',
  DIAMS = '\u2666'
}

type ExtraValue = string | RegConfig | number | number | RegConfig | boolean | null;

class TemplateElementValue {
  raw: string;

  cooked: string | RegConfig | number | boolean | null;

  constructor(raw: string, cooked: string | RegConfig | number | boolean | null) {
    this.raw = raw;
    this.cooked = cooked;
  }
}

class Node {
  id: Node | null = null;
  key: Node | null = null;
  test: Node | null = null;
  left: Node | null = null;
  right: Node | null = null;
  prefix: boolean | null = null;
  operator: string | RegConfig | number | boolean | null = null;
  consequent: Node[] | Node | null = null;
  alternate: Node | null = null;
  computed: boolean | null = null;
  kind: string | null = null;
  generator: boolean | null = null;
  expression: boolean | Node | null = null;
  expressions: Node[] | null = null;
  async: boolean | null = null;
  body: Node | Node[] | null = null;
  type: string;
  start: number;
  end: number;
  loc: SourceLocation;
  range: [number, number] | null = null;
  leadingComments: Comment[] | null = null;
  trailingComments: Comment[] | null = null;
  innerComments: Comment[] | null = null;
  sourceType: string | null = null;
  program: Node | null = null;
  tokens: (Token | Comment)[] | null = null;
  comments: Comment[] | null = null;
  value: string | Node | boolean | number | TemplateElementValue | RegConfig | null = null;
  extra: Map<string, ExtraValue> | null = null;
  argument: Node | null = null;
  superClass: Node | null = null;
  object: Node | null = null;
  callee: Node | null = null;
  args: Node[] | null = null;
  pattern: string | null = null;
  flags: string | null = null;
  meta: Node | null = null;
  property: Node | null = null;
  name: string | Node | null = null;
  tail: boolean | null = null;
  quasis: Node[] | null = null;
  decorators: Node[] | null = null;
  properties: Node[] | null = null;
  method: boolean | null = null;
  shorthand: boolean | null = null;
  params: (Node | null)[] | null = null;
  directives: Node[] | null = null;
  delegate: boolean | null = null;
  label: Node | null = null;
  declarations: Node[] | null = null;
  discriminant: Node | null = null;
  cases: Node[] | null = null;
  block: Node | null = null;
  handler: Node | null = null;
  finalizer: Node | null = null;
  specifiers: Node[] | null = null;
  exported: Node | null = null;
  statementStart: number | null = null;
  init: Node | null = null;
  update: Node | null = null;
  param: Node | null = null;
  guardedHandlers: null[] | null = null;
  await: boolean | null = null;
  static: boolean | null = null;
  typeAnnotation: Node | null = null;
  declaration: Node | null = null;
  source: Node | null = null;
  elements: Node[] | null = null;
  local: Node | null = null;
  imported: Node | null = null;
  tag: Node | null = null;
  quasi: Node | null = null;
  typeParameters: Node | null = null;
  rest: Node | null = null;
  returnType: Node | null = null;
  predicate: Node | null = null;
  mixins: Node[] | null = null;
  extendsArr: Node[] | null = null;
  variance: Node | null = null;
  bound: Node | null = null;
  default: Node | null = null;
  optional: boolean | null = null;
  callProperties: Node[] | null = null;
  indexers: Node[] | null = null;
  exact: boolean | null = null;
  qualification: Node | null = null;
  types: Node[] | null = null;
  elementType: Node | null = null;
  openingElement: Node | null = null;
  closingElement: Node | null = null;
  children: Node[] | null = null;
  selfClosing: boolean | null = null;
  namespace: Node | null = null;
  attributes: Node[] | null = null;
  regex: RegConfig | null = null;
  raw: string | null = null;
  directive: string | null = null;
  exportKind: string | null = null;
  _exprListItem: boolean | null = null;
  implements: Node[] | null = null;
  superTypeParameters: Node | null = null;
  importKind: string | null = null;

  constructor(parser: Parser | null, pos: number | null, loc: Position | null) {
    this.type = '';
    this.start = pos!;
    this.end = 0;
    this.loc = new SourceLocation(loc!, null);
    if (parser?.options.ranges) {
      this.range = [pos!, 0];
    }
    if (parser !== null && parser.fileName !== undefined) {
      this.loc.filename = parser.fileName;
    }
  }

  clone(): Node {
    let newNode: Node = new Node(null, null, null);
    newNode.type = this.type;
    newNode.start = this.start;
    newNode.end = this.end;
    newNode.loc = this.loc;
    newNode.range = this.range;
    return newNode;
  }
}

class Parser extends Tokenizer {
  fileName: string;

  primitiveTypes: string[] = ['any', 'mixed', 'empty', 'bool', 'boolean', 'number', 'string', 'void', 'null'];

  constructor(options: ParserOptions, input: string) {
    super(options, input);
    options = getOptions(options);
    this.fileName = options.sourceFilename!;
    this.options = options;
    this.inModule = this.options.sourceType === 'module';
    this.input = input;
    if (this.state.pos === 0 && this.input[0] === '#' && this.input[1] === '!') {
      this.skipLineComment(INT_VAL_P_2);
    }
  }

  hasPlugin(name: string): boolean {
    let excludePlugins = new Set<string>([
      'dynamicImport',
      'doExpressions',
      'functionSent',
      'objectRestSpread',
      'asyncGenerators',
      'decorators',
      'classProperties',
      'exportExtensions'
    ]);
    if (excludePlugins.has(name)) {
      return false;
    }
    return true;
  }

  isReservedWord(word: string): boolean {
    if (word === 'await') {
      return this.inModule;
    } else {
      return reservedWords.enumAwait(word);
    }
  }

  parse(): Node {
    let file = this.startNode();
    let program = this.startNode();
    this.nextToken();
    return this.parseTopLevel(file, program);
  }

  raise(pos: number, message: string): void {
    let newMessage = message;
    let loc = getLineInfo(this.input, pos);
    newMessage += ` (${loc.line}:${loc.column})`;
    let err = new ParserError(pos, loc, newMessage);
    throw err;
  }

  checkPropClash(prop: Node, propHash: Object): void {
    if (prop.computed || prop.kind) {
      return;
    }
  }

  getExpression(): Node {
    this.nextToken();
    let expr = this.parseExpression(null, null);
    if (!this.match(ttIns.eof)) {
      this.unexpected(null);
    }
    return expr;
  }

  parseExpression(noIn: boolean | null, refShorthandDefaultPos: ExpPos | null): Node {
    let startPos = this.state.start;
    let startLoc = this.state.startLoc!;
    let expr = this.parseMaybeAssign(noIn, refShorthandDefaultPos, null, null);
    if (this.match(ttIns.comma)) {
      let node = this.startNodeAt(startPos, startLoc);
      node.expressions = [expr];
      while (this.eat(ttIns.comma)) {
        node.expressions.push(this.parseMaybeAssign(noIn, refShorthandDefaultPos, null, null));
      }
      this.toReferencedList(node.expressions);
      return this.finishNode(node, 'SequenceExpression');
    }
    return expr;
  }

  parseMaybeAssign(
    noIn: boolean | null,
    refShorthandDefaultPosIn: ExpPos | null,
    afterLeftParse: ((tar: Parser, n: Node, pos: number, loc: Position) => Node) | null,
    refNeedsArrowPos: ExpPos | null
  ): Node {
    let refShorthandDefaultPos = refShorthandDefaultPosIn;
    let startPos = this.state.start;
    let startLoc = this.state.startLoc!;
    if (this.match(ttIns._yield) && this.state.inGenerator) {
      let left = this.parseYield();
      if (afterLeftParse !== null) {
        left = afterLeftParse(this, left, startPos, startLoc);
      }
      return left;
    }
    let failOnShorthandAssign: boolean;
    if (refShorthandDefaultPos !== null) {
      failOnShorthandAssign = false;
    } else {
      refShorthandDefaultPos = new ExpPos(0);
      failOnShorthandAssign = true;
    }
    if (this.match(ttIns.parenL) || this.match(ttIns.name)) {
      this.state.potentialArrowAt = this.state.start;
    }
    let left = this.parseMaybeConditional(noIn, refShorthandDefaultPos, refNeedsArrowPos);
    if (afterLeftParse) {
      left = afterLeftParse(this, left, startPos, startLoc);
    }
    if (this.state!.type!.isAssign) {
      let node = this.startNodeAt(startPos, startLoc);
      node.operator = this.state.value;
      node.left = this.match(ttIns.eq) ? this.toAssignable(left, null, 'assignment expression') : left;
      refShorthandDefaultPos.start = 0;
      this.checkLVal(left, null, null, 'assignment expression');
      if (left.extra && left.extra.get('parenthesized') !== undefined) {
        let errorMsg: string;
        if (left.type === 'ObjectPattern') {
          errorMsg = '`({a}) = 0` use `({a} = 0)`';
        } else if (left.type === 'ArrayPattern') {
          errorMsg = '`([a]) = 0` use `([a] = 0)`';
        }
        if (errorMsg!) {
          this.raise(left.start, `You're trying to assign to a parenthesized expression, eg. instead of ${errorMsg}`);
        }
      }
      this.next();
      node.right = this.parseMaybeAssign(noIn, null, null, null);
      return this.finishNode(node, 'AssignmentExpression');
    } else if (failOnShorthandAssign && refShorthandDefaultPos !== null && refShorthandDefaultPos.start !== 0) {
      this.unexpected(refShorthandDefaultPos.start);
    }
    return left;
  }

  parseMaybeConditional(noIn: boolean | null, refShorthandDefaultPos: ExpPos | null, refNeedsArrowPos: ExpPos | null): Node {
    let startPos = this.state.start;
    let startLoc = this.state.startLoc!;
    let expr = this.parseExprOps(noIn!, refShorthandDefaultPos);
    if (refShorthandDefaultPos && refShorthandDefaultPos.start) {
      return expr;
    }
    return this.parseConditional(expr, noIn, startPos, startLoc, null);
  }

  parseConditional(expr: Node, noIn: boolean | null, startPos: number, startLoc: Position, refNeedsArrowPos: State | null): Node {
    if (this.eat(ttIns.question)) {
      let node = this.startNodeAt(startPos, startLoc);
      node.test = expr;
      node.consequent = this.parseMaybeAssign(null, null, null, null);
      this.expect(ttIns.colon, null);
      node.alternate = this.parseMaybeAssign(noIn, null, null, null);
      return this.finishNode(node, 'ConditionalExpression');
    }
    return expr;
  }

  parseExprOps(noIn: boolean, refShorthandDefaultPos: ExpPos | null): Node {
    let startPos = this.state.start;
    let startLoc = this.state.startLoc;
    let expr = this.parseMaybeUnary(refShorthandDefaultPos);
    if (refShorthandDefaultPos !== null && refShorthandDefaultPos.start !== 0) {
      return expr;
    } else {
      return this.parseExprOp(expr, startPos, startLoc!, -1, noIn!);
    }
  }

  parseExprOp(left: Node, leftStartPos: number, leftStartLoc: Position, minPrec: number, noIn: boolean | null): Node {
    let prec = this.state.type!.binop;
    let inBool = false;
    if (noIn == null) {
      inBool = true;
    } else {
      inBool = !noIn!;
    }
    if (prec != null && (inBool || !this.match(ttIns._in))) {
      if (prec > minPrec) {
        let node = this.startNodeAt(leftStartPos, leftStartLoc);
        node.left = left;
        node.operator = this.state.value;
        if (
          node.operator === '**' &&
            left.type === 'UnaryExpression' &&
          left.extra &&
            !left.extra.get('parenthesizedArgument') &&
            !left.extra.get('parenthesized')
        ) {
          this.raise(left.argument!.start!, 'Illegal expression. Wrap left hand side or entire exponentiation in parentheses.');
        }
        let op = this.state.type;
        this.next();
        let startPos = this.state.start;
        let startLoc = this.state.startLoc;
        node.right = this.parseExprOp(this.parseMaybeUnary(null), startPos, startLoc!, op?.rightAssociative ? prec - 1 : prec, noIn);
        this.finishNode(node, op === ttIns.logicalOR || op === ttIns.logicalAND ? 'LogicalExpression' : 'BinaryExpression');
        return this.parseExprOp(node, leftStartPos, leftStartLoc, minPrec, noIn);
      }
    }
    return left;
  }

  parseMaybeUnary(refShorthandDefaultPos: ExpPos | null): Node {
    if (this.state?.type?.prefix) {
      let node = this.startNode();
      let update = this.match(ttIns.incDec);
      node.operator = this.state.value;
      node.prefix = true;
      this.next();
      let argType = this.state.type;
      node.argument = this.parseMaybeUnary(null);
      this.addExtra(node, 'parenthesizedArgument', argType === ttIns.parenL && (!node.argument.extra || !node.argument.extra.get('parenthesized')));
      if (refShorthandDefaultPos !== null && refShorthandDefaultPos.start !== 0) {
        this.unexpected(refShorthandDefaultPos.start);
      }
      if (update) {
        this.checkLVal(node.argument, null, null, 'prefix operation');
      } else if (this.state.strict && node.operator === 'delete' && node.argument.type === 'Identifier') {
        this.raise(node.start, 'Deleting local variable in strict mode');
      }
      return this.finishNode(node, update ? 'UpdateExpression' : 'UnaryExpression');
    }
    let startPos = this.state.start;
    let startLoc = this.state.startLoc;
    let expr = this.parseExprSubscripts(refShorthandDefaultPos);
    if (refShorthandDefaultPos !== null && refShorthandDefaultPos.start !== 0) {
      return expr;
    }
    while (this.state?.type?.postfix && !this.canInsertSemicolon()) {
      let node = this.startNodeAt(startPos, startLoc!);
      node.operator = this.state.value;
      node.prefix = false;
      node.argument = expr;
      this.checkLVal(expr, null, null, 'postfix operation');
      this.next();
      expr = this.finishNode(node, 'UpdateExpression');
    }
    return expr;
  }

  parseExprSubscripts(refShorthandDefaultPos: ExpPos | null): Node {
    let startPos = this.state.start;
    let startLoc = this.state.startLoc!;
    let potentialArrowAt = this.state.potentialArrowAt;
    let expr = this.parseExprAtom(refShorthandDefaultPos);
    if (expr!.type === 'ArrowFunctionExpression' && expr!.start === potentialArrowAt) {
      return expr!;
    }
    if (refShorthandDefaultPos !== null && refShorthandDefaultPos.start !== 0) {
      return expr!;
    }
    return this.parseSubscripts(expr!, startPos, startLoc, null);
  }

  parseSubscripts(baseIn: Node, startPos: number, startLoc: Position, noCalls: boolean | null): Node {
    let base = baseIn;
    let callsBool = false;
    if (noCalls == null) {
      callsBool = true;
    } else {
      callsBool = !noCalls!;
    }
    while (true) {
      if (!callsBool && this.eat(ttIns.doubleColon)) {
        let node = this.startNodeAt(startPos, startLoc);
        node.object = base;
        node.callee = this.parseNoCallExpr();
        return this.parseSubscripts(this.finishNode(node, 'BindExpression'), startPos, startLoc, noCalls);
      } else if (this.eat(ttIns.dot)) {
        let node = this.startNodeAt(startPos, startLoc);
        node.object = base;
        node.property = this.parseIdentifier(true);
        node.computed = false;
        base = this.finishNode(node, 'MemberExpression');
      } else if (this.eat(ttIns.bracketL)) {
        let node = this.startNodeAt(startPos, startLoc);
        node.object = base;
        node.property = this.parseExpression(null, null);
        node.computed = true;
        this.expect(ttIns.bracketR, null);
        base = this.finishNode(node, 'MemberExpression');
      } else if (!noCalls && this.match(ttIns.parenL)) {
        let possibleAsync = this.state.potentialArrowAt === base.start && base.type === 'Identifier' && base.name === 'async' && !this.canInsertSemicolon();
        this.next();
        let node = this.startNodeAt(startPos, startLoc);
        node.callee = base;
        node.args = this.parseCallExpressionArguments(ttIns.parenR, possibleAsync);
        if (node.callee.type === 'Import' && node.args.length !== 1) {
          this.raise(node.start, 'import() requires exactly one argument');
        }
        base = this.finishNode(node, 'CallExpression');
        if (possibleAsync && this.shouldParseAsyncArrow()) {
          return this.parseAsyncArrowFromCallExpression(this.startNodeAt(startPos, startLoc), node);
        } else {
          this.toReferencedList(node.args);
        }
      } else if (this.match(ttIns.backQuote)) {
        let node = this.startNodeAt(startPos, startLoc);
        node.tag = base;
        node.quasi = this.parseTemplate(true);
        base = this.finishNode(node, 'TaggedTemplateExpression');
      } else {
        return base;
      }
    }
  }

  parseCallExpressionArguments(close: TokenType, possibleAsyncArrow: boolean): Node[] {
    let elts = new Array<Node>();
    let innerParenStart: number = 0;
    let first = true;
    while (!this.eat(close)) {
      if (first) {
        first = false;
      } else {
        this.expect(ttIns.comma, null);
        if (this.eat(close)) {
          break;
        }
      }
      if (this.match(ttIns.parenL) && innerParenStart) {
        innerParenStart = this.state.start;
      }
      let node = this.parseExprListItem(false, possibleAsyncArrow ? new ExpPos(0) : null, possibleAsyncArrow ? new ExpPos(0) : null)!;
      elts.push(node);
    }
    if (possibleAsyncArrow && innerParenStart && this.shouldParseAsyncArrow()) {
      this.unexpected(null);
    }
    return elts;
  }

  shouldParseAsyncArrow(): boolean {
    return this.match(ttIns.arrow);
  }

  parseAsyncArrowFromCallExpression(node: Node, call: Node): Node {
    this.expect(ttIns.arrow, null);
    return this.parseArrowExpression(node, call.args!, true);
  }

  parseNoCallExpr(): Node {
    let startPos = this.state.start;
    let startLoc = this.state.startLoc;
    return this.parseSubscripts(this.parseExprAtom(null)!, startPos, startLoc!, true);
  }

  parseExprAtom(refShorthandDefaultPos: ExpPos | null): Node | null {
    let canBeArrow = this.state.potentialArrowAt === this.state.start;
    let node: Node;
    switch (this.state.type) {
      case ttIns._super:
        if (!this.state.inMethod && !this.options.allowSuperOutsideMethod) {
          this.raise(this.state.start, "'super' outside of function or class");
        }
        node = this.startNode();
        this.next();
        if (!this.match(ttIns.parenL) && !this.match(ttIns.bracketL) && !this.match(ttIns.dot)) {
          this.unexpected(null);
        }
        if (this.match(ttIns.parenL) && this.state.inMethod !== 'constructor' && !this.options.allowSuperOutsideMethod) {
          this.raise(node.start, "super() is only valid inside a class constructor. Make sure the method name is spelled exactly as 'constructor'.");
        }
        return this.finishNode(node, 'Super');
      case ttIns._import:
        if (!this.hasPlugin('dynamicImport')) {
          this.unexpected(null);
        }
        node = this.startNode();
        this.next();
        if (!this.match(ttIns.parenL)) {
          this.unexpected(null, ttIns.parenL);
        }
        return this.finishNode(node, 'Import');
      case ttIns._this:
        node = this.startNode();
        this.next();
        return this.finishNode(node, 'ThisExpression');
      case ttIns._yield:
        if (this.state.inGenerator) {
          this.unexpected(null);
        }
      case ttIns.name:
        node = this.startNode();
        let allowAwait = this.state.value === 'await' && this.state.inAsync;
        let allowYield = this.shouldAllowYieldIdentifier();
        let id = this.parseIdentifier(allowAwait || allowYield);
        if (id.name === 'await') {
          if (this.state.inAsync || this.inModule) {
            return this.parseAwait(node);
          }
        } else if (id.name === 'async' && this.match(ttIns._function) && !this.canInsertSemicolon()) {
          this.next();
          return this.parseFunction(node, false, false, true, null);
        } else if (canBeArrow && id.name === 'async' && this.match(ttIns.name)) {
          let params = [this.parseIdentifier(null)];
          this.expect(ttIns.arrow, null);
          return this.parseArrowExpression(node, params, true);
        }
        if (canBeArrow && !this.canInsertSemicolon() && this.eat(ttIns.arrow)) {
          return this.parseArrowExpression(node, [id], null);
        }
        return id!;
      case ttIns._do:
        if (this.hasPlugin('doExpressions')) {
          let node = this.startNode();
          this.next();
          let oldInFunction = this.state.inFunction;
          let oldLabels = this.state.labels;
          this.state.labels = new Array();
          this.state.inFunction = false;
          node.body = this.parseBlock(false);
          this.state.inFunction = oldInFunction;
          this.state.labels = oldLabels;
          return this.finishNode(node, 'DoExpression');
        }
      case ttIns.regexp:
        let value = this.state.value;
        node = this.parseLiteral((value as RegConfig).value, 'RegExpLiteral', null, null);
        node.pattern = (value as RegConfig).pattern;
        node.flags = (value as RegConfig).flags;
        return node!;
      case ttIns.num:
        return this.parseLiteral(this.state.value!, 'NumericLiteral', null, null);
      case ttIns.string:
        return this.parseLiteral(this.state.value!, 'StringLiteral', null, null);
      case ttIns._null:
        node = this.startNode();
        this.next();
        return this.finishNode(node, 'NullLiteral');
      case ttIns._true:
      case ttIns._false:
        node = this.startNode();
        node.value = this.match(ttIns._true);
        this.next();
        return this.finishNode(node, 'BooleanLiteral');
      case ttIns.parenL:
        return this.parseParenAndDistinguishExpression(null, null, canBeArrow);
      case ttIns.bracketL:
        node = this.startNode();
        this.next();
        let expList = this.parseExprList(ttIns.bracketR, true, refShorthandDefaultPos);
        node.elements = expList;
        this.toReferencedList(expList);
        return this.finishNode(node, 'ArrayExpression');
      case ttIns.braceL:
        return this.parseObj(false, refShorthandDefaultPos);
      case ttIns._function:
        return this.parseFunctionExpression();
      case ttIns.at:
        this.parseDecorators(null);
        return null;
      case ttIns._class:
        node = this.startNode();
        this.takeDecorators(node);
        return this.parseClass(node, false, null);
      case ttIns._new:
        return this.parseNew();
      case ttIns.backQuote:
        return this.parseTemplate(false);
      case ttIns.doubleColon:
        node = this.startNode();
        this.next();
        node.object = null;
        node.callee = this.parseNoCallExpr();
        let callee = node.callee;
        if (callee.type === 'MemberExpression') {
          return this.finishNode(node, 'BindExpression');
        } else {
          this.raise(callee.start, 'Binding should be performed on object property.');
          return null;
        }
      default:
        this.unexpected(null);
        return null;
    }
  }

  parseFunctionExpression(): Node {
    let node = this.startNode();
    let meta = this.parseIdentifier(true);
    if (this.state.inGenerator && this.eat(ttIns.dot) && this.hasPlugin('functionSent')) {
      return this.parseMetaProperty(node, meta, 'sent');
    } else {
      return this.parseFunction(node, false, null, null, null);
    }
  }

  parseMetaProperty(node: Node, meta: Node, propertyName: string): Node {
    node.meta = meta;
    node.property = this.parseIdentifier(true);
    if (node.property.name !== propertyName) {
      this.raise(node.property.start, `The only valid meta property for new is ${meta.name}.${propertyName}`);
    }
    return this.finishNode(node, 'MetaProperty');
  }
  parseLiteral(value: string | number | RegConfig | boolean | null, type: string, startPos: number | null, startLoc: Position | null): Node {
    startPos = startPos !== null ? startPos : this.state.start;
    startLoc = startLoc !== null ? startLoc : this.state.startLoc;
    let node = this.startNodeAt(startPos, startLoc!);
    this.addExtra(node, 'rawValue', value);
    this.addExtra(node, 'raw', this.input.slice(startPos, this.state.end));
    node.value = value;
    this.next();
    return this.finishNode(node, type);
  }

  parseParenExpression(): Node {
    this.expect(ttIns.parenL, null);
    let val = this.parseExpression(null, null);
    this.expect(ttIns.parenR, null);
    return val;
  }

  parseParenAndDistinguishExpression(startPos: number | null, startLoc: Position | null, canBeArrow: boolean): Node {
    startPos = startPos !== null ? startPos : this.state.start;
    startLoc = startLoc !== null ? startLoc : this.state.startLoc!;
    let val: Node;
    this.expect(ttIns.parenL, null);
    let innerStartPos = this.state.start;
    let innerStartLoc = this.state.startLoc;
    let exprList: Node[] = new Array();
    let refShorthandDefaultPos = new ExpPos(0);
    let refNeedsArrowPos = new ExpPos(0);
    let first = true;
    let spreadStart: number = 0;
    let optionalCommaStart: number = 0;
    while (!this.match(ttIns.parenR)) {
      if (first) {
        first = false;
      } else {
        this.expect(ttIns.comma, refNeedsArrowPos.start || null);
        if (this.match(ttIns.parenR)) {
          optionalCommaStart = this.state.start;
          break;
        }
      }
      if (this.match(ttIns.ellipsis)) {
        let spreadNodeStartPos = this.state.start;
        let spreadNodeStartLoc = this.state.startLoc;
        spreadStart = this.state.start;
        exprList.push(this.parseParenItem(this.parseRest(), null, null));
        break;
      } else {
        let handler: (tar: Parser, n: Node, pos: number, loc: Position) => Node = (tar: Parser, n: Node, pos: number, loc: Position) => {
          return n;
        };
        exprList.push(this.parseMaybeAssign(false, refShorthandDefaultPos, handler, refNeedsArrowPos));
      }
    }

    let innerEndPos = this.state.start;
    let innerEndLoc = this.state.startLoc;
    this.expect(ttIns.parenR, null);
    let arrowNode: Node | null = this.startNodeAt(startPos, startLoc!);

    if (canBeArrow && this.shouldParseArrow()) {
      arrowNode = this.parseArrow(arrowNode);
      if (arrowNode !== null) {
        for (let param of exprList) {
          if (param.extra != null && param.extra.get('parenthesized') != null) {
            this.unexpected(param.extra.get('parenStart') as number);
          }
        }
        return this.parseArrowExpression(arrowNode, exprList, null);
      }
    }
    if (!exprList.length) {
      this.unexpected(this.state.lastTokStart);
    }
    if (optionalCommaStart) {
      this.unexpected(optionalCommaStart);
    }
    if (spreadStart) {
      this.unexpected(spreadStart);
    }
    if (refShorthandDefaultPos.start !== 0) {
      this.unexpected(refShorthandDefaultPos.start);
    }
    if (refNeedsArrowPos.start) {
      this.unexpected(refNeedsArrowPos.start);
    }
    if (exprList.length > 1) {
      val = this.startNodeAt(innerStartPos, innerStartLoc);
      val.expressions = exprList;
      this.toReferencedList(val.expressions);
      this.finishNodeAt(val, 'SequenceExpression', innerEndPos, innerEndLoc!);
    } else {
      val = exprList[0];
    }
    this.addExtra(val, 'parenthesized', true);
    this.addExtra(val, 'parenStart', startPos);
    return val;
  }

  shouldParseArrow(): boolean {
    return !this.canInsertSemicolon();
  }

  parseArrow(node: Node): Node | null {
    if (this.eat(ttIns.arrow)) {
      return node;
    }
    return null;
  }

  parseParenItem(node: Node, startPos: number | null, startLoc: Position | null): Node {
    return node;
  }

  parseNew(): Node {
    let node = this.startNode();
    let meta = this.parseIdentifier(true);
    if (this.eat(ttIns.dot)) {
      let metaProp = this.parseMetaProperty(node, meta, 'target');
      if (!this.state?.inFunction) {
        this.raise(metaProp!.property!.start, 'new.target can only be used in functions');
      }
      return metaProp;
    }
    node.callee = this.parseNoCallExpr();
    if (this.eat(ttIns.parenL)) {
      node.args = this.parseExprList(ttIns.parenR, null, null);
      this.toReferencedList(node.args);
    } else {
      node.args = new Array();
    }
    return this.finishNode(node, 'NewExpression');
  }

  parseTemplateElement(isTagged: boolean): Node {
    let elem = this.startNode();
    if (this.state.value === null) {
      if (!isTagged) {
        this.raise(this.state.invalidTemplateEscapePosition!, 'Invalid escape sequence in template');
      } else {
        this.state.invalidTemplateEscapePosition = null;
      }
    }
    let reg = new RegExp('\\r\\n?', 'g');
    let raw = this.input.slice(this.state.start, this.state.end).replace(reg, '\n');
    let cooked = this.state.value;
    elem.value = new TemplateElementValue(raw, cooked!);
    this.next();
    elem.tail = this.match(ttIns.backQuote);
    return this.finishNode(elem, 'TemplateElement');
  }

  parseTemplate(isTagged: boolean): Node {
    let node = this.startNode();
    this.next();
    node.expressions = new Array();
    let curElt = this.parseTemplateElement(isTagged);
    node.quasis = [curElt];
    while (!curElt.tail) {
      this.expect(ttIns.dollarBraceL, null);
      node.expressions.push(this.parseExpression(null, null));
      this.expect(ttIns.braceR, null);
      curElt = this.parseTemplateElement(isTagged);
      node.quasis.push(curElt);
    }
    this.next();
    return this.finishNode(node, 'TemplateLiteral');
  }

  parseObj(isPattern: boolean, refShorthandDefaultPos: ExpPos | null): Node {
    let decorators: Node[] = new Array();
    let propHash: Object = new Object();
    let first = true;
    let node = this.startNode();
    node.properties = new Array();
    this.next();
    let firstRestLoc: number | null = null;
    while (!this.eat(ttIns.braceR)) {
      if (first) {
        first = false;
      } else {
        this.expect(ttIns.comma, null);
        if (this.eat(ttIns.braceR)) {
          break;
        }
      }
      while (this.match(ttIns.at)) {
        decorators.push(this.parseDecorator());
      }
      let prop = this.startNode();
      let isGenerator = false;
      let isAsync = false;
      let startPos: number;
      let startLoc: Position;
      if (decorators.length) {
        prop.decorators = decorators;
        decorators = new Array();
      }
      if (this.hasPlugin('objectRestSpread') && this.match(ttIns.ellipsis)) {
        prop = this.parseSpread(isPattern ? new ExpPos(0) : null);
        prop.type = isPattern ? 'RestElement' : 'SpreadElement';
        if (isPattern) {
          this.toAssignable(prop.argument!, true, 'object pattern');
        }
        node.properties.push(prop);
        if (isPattern) {
          let position = this.state.start;
          if (firstRestLoc !== null) {
            this.unexpected(firstRestLoc, 'Cannot have multiple rest elements when destructuring');
          } else if (this.eat(ttIns.braceR)) {
            break;
          } else if (this.match(ttIns.comma) && this.lookahead().type === ttIns.braceR) {
            this.unexpected(position, 'A trailing comma is not permitted after the rest element');
          } else {
            firstRestLoc = position;
            continue;
          }
        } else {
          continue;
        }
      }
      prop.method = false;
      prop.shorthand = false;
      if (isPattern || refShorthandDefaultPos !== null) {
        startPos = this.state.start!;
        startLoc = this.state.startLoc!;
      }
      if (!isPattern) {
        isGenerator = this.eat(ttIns.star);
      }
      if (!isPattern && this.isContextual('async')) {
        if (isGenerator) {
          this.unexpected(null);
        }
        let asyncId = this.parseIdentifier(null);
        if (this.match(ttIns.colon) || this.match(ttIns.parenL) || this.match(ttIns.braceR) || this.match(ttIns.eq) || this.match(ttIns.comma)) {
          prop.key = asyncId;
          prop.computed = false;
        } else {
          isAsync = true;
          if (this.hasPlugin('asyncGenerators')) {
            isGenerator = this.eat(ttIns.star);
          }
          this.parsePropertyName(prop);
        }
      } else {
        this.parsePropertyName(prop);
      }
      this.parseObjPropValue(prop, startPos!, startLoc!, isGenerator, isAsync, isPattern, refShorthandDefaultPos!);
      this.checkPropClash(prop, propHash);
      if (prop.shorthand) {
        this.addExtra(prop, 'shorthand', true);
      }
      node.properties.push(prop);
    }
    if (firstRestLoc !== null) {
      this.unexpected(firstRestLoc, 'The rest element has to be the last element when destructuring');
    }
    if (decorators.length) {
      this.raise(this.state.start, 'You have trailing decorators with no property');
    }
    return this.finishNode(node, isPattern ? 'ObjectPattern' : 'ObjectExpression');
  }

  isGetterOrSetterMethod(prop: Node, isPattern: boolean): boolean {
    return (
      !isPattern &&
        !prop.computed &&
        prop!.key!.type === 'Identifier' &&
        (prop!.key!.name === 'get' || prop!.key!.name === 'set') &&
        (this.match(ttIns.string) || // get "string"() {}
        this.match(ttIns.num) || // get 1() {}
        this.match(ttIns.bracketL) || // get ["string"]() {}
        this.match(ttIns.name) || // get foo() {}
          this.state!.type?.keyword !== null) // get debugger() {}
    );
  }

  checkGetterSetterParamCount(method: Node): void {
    let paramCount = method.kind === 'get' ? 0 : 1;
    if (method.params!.length !== paramCount) {
      let start = method.start;
      if (method.kind === 'get') {
        this.raise(start, 'getter should have no params');
      } else {
        this.raise(start, 'setter should have exactly one param');
      }
    }
  }

  parseObjectMethod(prop: Node, isGenerator: boolean, isAsync: boolean, isPattern: boolean): Node | null {
    if (isAsync || isGenerator || this.match(ttIns.parenL)) {
      if (isPattern) {
        this.unexpected(null);
      }
      prop.kind = 'method';
      prop.method = true;
      this.parseMethod(prop, isGenerator, isAsync);
      return this.finishNode(prop, 'ObjectMethod');
    }

    if (this.isGetterOrSetterMethod(prop, isPattern)) {
      if (isGenerator || isAsync) {
        this.unexpected(null);
      }
      prop.kind = prop!.key!.name as string;
      this.parsePropertyName(prop);
      this.parseMethod(prop, null, null);
      this.checkGetterSetterParamCount(prop);
      return this.finishNode(prop, 'ObjectMethod');
    }
    return null;
  }

  parseObjectProperty(prop: Node, startPos: number, startLoc: Position, isPattern: boolean, refShorthandDefaultPos: ExpPos): Node | null {
    if (this.eat(ttIns.colon)) {
      prop.value = isPattern ?
        this.parseMaybeDefault(this.state.start, this.state.startLoc, null) :
        this.parseMaybeAssign(false, refShorthandDefaultPos, null, null);
      return this.finishNode(prop, 'ObjectProperty');
    }
    if (!prop.computed! && prop.key!.type === 'Identifier') {
      if (isPattern) {
        this.checkReservedWord(prop.key!.name! as string, prop.key!.start, true, true);
        prop.value = this.parseMaybeDefault(startPos, startLoc, prop.key!.clone());
      } else if (this.match(ttIns.eq) && refShorthandDefaultPos !== null) {
        if (refShorthandDefaultPos.start === 0) {
          refShorthandDefaultPos.start = this.state.start;
        }
        prop.value = this.parseMaybeDefault(startPos, startLoc, prop.key!.clone());
      } else {
        prop.value = prop.key!.clone();
      }
      prop.shorthand = true;
      return this.finishNode(prop, 'ObjectProperty');
    }
    return null;
  }

  parseObjPropValue(
    prop: Node,
    startPos: number,
    startLoc: Position,
    isGenerator: boolean,
    isAsync: boolean,
    isPattern: boolean,
    refShorthandDefaultPos: ExpPos
  ): Node | null {
    let node =
      this.parseObjectMethod(prop, isGenerator, isAsync, isPattern) || this.parseObjectProperty(prop, startPos, startLoc, isPattern, refShorthandDefaultPos);
    if (!node) {
      this.unexpected(null);
    }
    return node;
  }

  parsePropertyName(prop: Node): Node {
    if (this.eat(ttIns.bracketL)) {
      prop.computed = true;
      prop.key = this.parseMaybeAssign(null, null, null, null);
      this.expect(ttIns.bracketR, null);
    } else {
      prop.computed = false;
      let oldInPropertyName = this.state.inPropertyName;
      this.state.inPropertyName = true;
      prop.key = this.match(ttIns.num) || this.match(ttIns.string) ? this.parseExprAtom(null) : this.parseIdentifier(true);
      this.state.inPropertyName = oldInPropertyName;
    }
    return prop.key!;
  }

  initFunction(node: Node, isAsync: boolean | null): void {
    node.id = null;
    node.generator = false;
    node.expression = false;
    node.async = isAsync === null ? false : isAsync;
  }

  parseMethod(node: Node, isGenerator: boolean | null, isAsync: boolean | null): Node {
    let oldInMethod = this.state.inMethod;
    this.state.inMethod = node.kind !== null ? node.kind : true;
    this.initFunction(node, isAsync);
    this.expect(ttIns.parenL, null);
    node.params = this.parseBindingList(ttIns.parenR, null);
    node.generator = isGenerator === null ? false : isGenerator;
    this.parseFunctionBody(node, null);
    this.state.inMethod = oldInMethod;
    return node;
  }

  parseArrowExpression(node: Node, params: Node[], isAsync: boolean | null): Node {
    this.initFunction(node, isAsync);
    node.params = this.toAssignableList(params, true, 'arrow function parameters');
    this.parseFunctionBody(node, true);
    return this.finishNode(node, 'ArrowFunctionExpression');
  }

  isStrictBody(node: Node, isExpression: boolean): boolean {
    if (!isExpression && (node.body as Node).directives!.length) {
      for (let directive of (node.body as Node).directives!) {
        if ((directive.value as Node).value === 'use strict') {
          return true;
        }
      }
    }
    return false;
  }

  parseFunctionBody(node: Node, allowExpression: boolean | null): void {
    let allowExpressionBool = allowExpression === null ? false : allowExpression;
    let isExpression = allowExpressionBool && !this.match(ttIns.braceL);
    let oldInAsync = this.state.inAsync;
    this.state.inAsync = node.async!;
    if (isExpression) {
      node.body = this.parseMaybeAssign(null, null, null, null);
      node.expression = true;
    } else {
      let oldInFunc = this.state.inFunction;
      let oldInGen = this.state.inGenerator;
      let oldLabels = this.state.labels;
      this.state.inFunction = true;
      this.state.inGenerator = node.generator!;
      this.state.labels = new Array();
      node.body = this.parseBlock(true);
      node.expression = false;
      this.state.inFunction = oldInFunc;
      this.state.inGenerator = oldInGen;
      this.state.labels = oldLabels;
    }
    this.state.inAsync = oldInAsync;
    let isStrict = this.isStrictBody(node, isExpression);
    let checkLVal = this.state.strict || allowExpressionBool || isStrict;
    if (isStrict && node.id && node.id.type === 'Identifier' && node.id.name === 'yield') {
      this.raise(node.id.start, 'Binding yield in strict mode');
    }
    if (checkLVal) {
      let nameHash: Map<string, boolean> = new Map();
      let oldStrict = this.state.strict;
      if (isStrict) {
        this.state.strict = true;
      }
      if (node.id) {
        this.checkLVal(node.id, true, null, 'function name');
      }
      for (let param of node.params!) {
        if (isStrict && param!.type !== 'Identifier') {
          this.raise(param!.start, 'Non-simple parameter in strict mode');
        }
        this.checkLVal(param!, true, nameHash, 'function parameter list');
      }
      this.state.strict = oldStrict;
    }
  }

  parseExprList(close: TokenType, allowEmpty: boolean | null, refShorthandDefaultPos: ExpPos | null): Node[] {
    let elts: Node[] = new Array();
    let first = true;
    while (!this.eat(close)) {
      if (first) {
        first = false;
      } else {
        this.expect(ttIns.comma, null);
        if (this.eat(close)) {
          break;
        }
      }
      elts.push(this.parseExprListItem(allowEmpty!, refShorthandDefaultPos, null)!);
    }
    return elts;
  }

  parseExprListItem(allowEmpty: boolean, refShorthandDefaultPos: ExpPos | null, refNeedsArrowPos: ExpPos | null): Node | null {
    let elt: Node | null = null;
    if (allowEmpty && this.match(ttIns.comma)) {
      elt = null;
    } else if (this.match(ttIns.ellipsis)) {
      elt = this.parseSpread(refShorthandDefaultPos);
    } else {
      let handler: (tar: Parser, n: Node, pos: number, loc: Position) => Node = (tar: Parser, n: Node, pos: number, loc: Position) => {
        return this.parseParenItem(n, null, null);
      };
      elt = this.parseMaybeAssign(false, refShorthandDefaultPos, handler, refNeedsArrowPos);
    }
    return elt;
  }

  parseIdentifier(liberal: boolean | null): Node {
    let node = this.startNode();
    let liberalBool = liberal === null ? true : !liberal;
    let keyWord = false;
    if (this.state.type?.keyword === null) {
      keyWord = false;
    } else {
      if (this.state.type?.keyword?.length === 0) {
        keyWord = false;
      } else {
        keyWord = true;
      }
    }
    if (liberalBool) {
      this.checkReservedWord(this.state.value as string, this.state.start, keyWord, false);
    }
    if (this.match(ttIns.name)) {
      node.name = this.state.value as string;
    } else if (this.state.type!.keyword !== null) {
      node.name = this.state.type!.keyword;
    } else {
      this.unexpected(null);
    }
    if (liberalBool && node.name === 'await' && this.state.inAsync) {
      this.raise(node.start, 'invalid use of await inside of an async function');
    }
    node.loc.identifierName = node.name as string;
    this.next();
    return this.finishNode(node, 'Identifier');
  }

  checkReservedWord(word: string, startLoc: number, checkKeywords: boolean, isBinding: boolean): void {
    if (this.isReservedWord(word) || (checkKeywords && this.isKeyword(word))) {
      this.raise(startLoc, word + ' is a reserved word');
    }
    if (this.state.strict && (reservedWords.strict(word) || (isBinding && reservedWords.strictBind(word)))) {
      this.raise(startLoc, word + ' is a reserved word in strict mode');
    }
  }

  parseAwait(node: Node): Node {
    if (!this.state.inAsync) {
      this.unexpected(null);
    }
    if (this.match(ttIns.star)) {
      this.raise(node.start, 'await* has been removed from the async functions proposal. Use Promise.all() instead.');
    }
    node.argument = this.parseMaybeUnary(null);
    return this.finishNode(node, 'AwaitExpression');
  }

  parseYield(): Node {
    let node = this.startNode();
    this.next();
    if (this.match(ttIns.semi) || this.canInsertSemicolon() || (!this.match(ttIns.star) && !this.state.type!.startsExpr)) {
      node.delegate = false;
      node.argument = null;
    } else {
      node.delegate = this.eat(ttIns.star);
      node.argument = this.parseMaybeAssign(null, null, null, null);
    }
    return this.finishNode(node, 'YieldExpression');
  }

  startNode(): Node {
    return new Node(this, this.state.start, this.state.startLoc);
  }

  startNodeAt(pos: number | null, loc: Position | null): Node {
    return new Node(this, pos, loc);
  }

  finishNode(node: Node, type: string): Node {
    node.type = type;
    node.end = this.state.lastTokEnd;
    node.loc.end = this.state.lastTokEndLoc;
    if (this.options.ranges) {
      node.range![1] = this.state.lastTokEnd;
    }
    this.processComment(node);
    return node;
  }

  finishNodeAt(node: Node, type: string, pos: number, loc: Position): Node {
    node.type = type;
    node.end = pos;
    node.loc.end = loc;
    if (this.options.ranges) {
      node.range![1] = pos;
    }
    this.processComment(node);
    return node;
  }

  resetStartLocationFromNode(node: Node, locationNode: Node): Node {
    node.start = locationNode.start;
    node.loc.start = locationNode.loc.start;
    if (this.options.ranges) {
      node.range![0] = locationNode.range![0];
    }
    return node;
  }

  addComment(comment: Comment): void {
    comment.loc.filename = this.fileName;
    this.state.trailingComments!.push(comment);
    this.state.leadingComments!.push(comment);
  }

  processComment(nNode: Node): void {
    if (nNode.type === 'Program' && (nNode.body as Node[]).length > 0) {
      return;
    }
    let stack = this.state.commentStack!;
    let lastChild: Node | null = null;
    let trailingCommentArray: Comment[] | null = null;
    let i: number = 0;
    let j: number = 0;
    if (this.state.trailingComments!.length > 0) {
      if (this.state.trailingComments![0].start >= nNode.end) {
        trailingCommentArray = this.state.trailingComments;
        this.state.trailingComments = new Array();
      } else {
        this.state.trailingComments!.length = 0;
      }
    } else {
      let lastInStack = last(stack);
      if (
        stack.length > 0 &&
          lastInStack.trailingComments !== null &&
          lastInStack.trailingComments.length > 0 &&
          lastInStack.trailingComments[0].start >= nNode.end
      ) {
        trailingCommentArray = lastInStack.trailingComments;
        lastInStack.trailingComments = null;
      }
    }
    while (stack.length > 0 && last(stack).start >= nNode.start) {
      lastChild = stack.pop() || null;
    }
    if (lastChild) {
      if (lastChild.leadingComments) {
        if (lastChild !== nNode && last(lastChild.leadingComments).end <= nNode.start) {
          nNode.leadingComments = lastChild.leadingComments;
          lastChild.leadingComments = null;
        } else {
          i = lastChild.leadingComments.length - INT_VAL_P_2;
          while (i >= 0) {
            if (lastChild.leadingComments[i].end <= nNode.start) {
              nNode.leadingComments = lastChild.leadingComments.splice(0, i + 1);
              break;
            }
            i -= 1;
          }
        }
      }
    } else if (this.state.leadingComments!.length > 0) {
      if (last(this.state.leadingComments!).end <= nNode.start) {
        if (this.state.commentPreviousNode) {
          while (j < this.state.leadingComments!.length) {
            if (this.state.leadingComments![j].end < this.state.commentPreviousNode!.end) {
              this.state.leadingComments!.splice(j, 1);
              j -= 1;
            }
            j++;
          }
        }
        if (this.state.leadingComments!.length > 0) {
          nNode.leadingComments = this.state.leadingComments;
          this.state.leadingComments = new Array();
        }
      } else {
        for (i = 0; i < this.state.leadingComments!.length; i++) {
          if (this.state.leadingComments![i].end > nNode.start) {
            break;
          }
        }
        nNode.leadingComments = this.state.leadingComments!.slice(0, i);
        if (nNode.leadingComments!.length === 0) {
          nNode.leadingComments = null;
        }
        trailingCommentArray = this.state.leadingComments!.slice(i);
        if (trailingCommentArray.length === 0) {
          trailingCommentArray = null;
        }
      }
    }
    this.state.commentPreviousNode = nNode;
    if (trailingCommentArray) {
      if (trailingCommentArray.length && trailingCommentArray[0].start >= nNode.start && last(trailingCommentArray).end <= nNode.end) {
        nNode.innerComments = trailingCommentArray;
      } else {
        nNode.trailingComments = trailingCommentArray;
      }
    }
    stack.push(nNode);
    this.state.commentStack = stack;
  }

  addExtra(node: Node, key: string, val: ExtraValue): void {
    node.extra = node.extra || new Map();
    let extra = node.extra;
    extra[key] = val;
  }

  isRelational(op: string): boolean {
    return this.match(ttIns.relational) && this.state.value === op;
  }

  expectRelational(op: string): void {
    if (this.isRelational(op)) {
      this.next();
    } else {
      this.unexpected(null, ttIns.relational);
    }
  }

  isContextual(name: string): boolean {
    return this.match(ttIns.name) && this.state.value === name;
  }

  eatContextual(name: string): boolean {
    return this.state.value === name && this.eat(ttIns.name);
  }

  expectContextual(name: string, message: string | null): void {
    if (!this.eatContextual(name)) {
      this.unexpected(null, message);
    }
  }

  canInsertSemicolon(): boolean {
    return this.match(ttIns.eof) || this.match(ttIns.braceR) || lineBreak.test(this.input.slice(this.state.lastTokEnd, this.state.start));
  }

  isLineTerminator(): boolean {
    return this.eat(ttIns.semi) || this.canInsertSemicolon();
  }

  semicolon(): void {
    if (!this.isLineTerminator()) {
      this.unexpected(null, ttIns.semi);
    }
  }

  expect(type: TokenType, pos: number | null): null | boolean {
    let eatBool = this.eat(type);
    if (eatBool) {
      return eatBool;
    } else {
      this.unexpected(pos, type);
      return null;
    }
  }

  unexpected(pos: number | null, messageOrType: TokenType | string | null = 'Unexpected token'): null {
    if (messageOrType !== null && typeof messageOrType === 'object' && messageOrType.label.length > 0) {
      messageOrType = `Unexpected token, expected ${messageOrType.label}`;
    }
    this.raise(pos !== null ? pos : this.state.start, messageOrType as string);
    return null;
  }

  toAssignable(node: Node, isBinding: boolean | null, contextDescription: string): Node {
    if (node) {
      switch (node.type) {
        case 'Identifier':
        case 'ObjectPattern':
        case 'ArrayPattern':
        case 'AssignmentPattern':
          break;

        case 'ObjectExpression':
          node.type = 'ObjectPattern';
          for (let prop of (node as Node).properties!) {
            if (prop.type === 'ObjectMethod') {
              if (prop.kind === 'get' || prop.kind === 'set') {
                this.raise(prop.key!.start, "Object pattern can't contain getter or setter");
              } else {
                this.raise(prop.key!.start, "Object pattern can't contain methods");
              }
            } else {
              this.toAssignable(prop, isBinding, 'object destructuring pattern');
            }
          }
          break;

        case 'ObjectProperty':
          this.toAssignable(node.value as Node, isBinding, contextDescription);
          break;

        case 'SpreadElement':
          node.type = 'RestElement';
          break;

        case 'ArrayExpression':
          node.type = 'ArrayPattern';
          this.toAssignableList(node.elements!, isBinding!, contextDescription);
          break;

        case 'AssignmentExpression':
          if (node.operator === '=') {
            node.type = 'AssignmentPattern';
            node.operator = null;
          } else {
            this.raise(node.left!.end, "Only '=' operator can be used for specifying default value.");
          }
          break;

        case 'MemberExpression':
          if (!isBinding) {
            break;
          }
        default: {
          let message = 'Invalid left-hand side' + (contextDescription ? ' in ' + contextDescription : /* istanbul ignore next */ 'expression');
          this.raise(node.start, message);
        }
      }
    }
    return node;
  }

  toAssignableList(exprList: (Node | null)[], isBinding: boolean, contextDescription: string): Node[] {
    let end = exprList.length;
    if (end) {
      let last = exprList[end - 1];
      if (last && last.type === 'RestElement') {
        end -= 1;
      } else if (last && last.type === 'SpreadElement') {
        last.type = 'RestElement';
        let arg = last.argument!;
        this.toAssignable(arg, isBinding, contextDescription);
        if (arg.type !== 'Identifier' && arg.type !== 'MemberExpression' && arg.type !== 'ArrayPattern') {
          this.unexpected(arg.start);
        }
        end -= 1;
      }
    }
    for (let i = 0; i < end; i++) {
      let elt = exprList[i];
      if (elt != null && elt.type === 'SpreadElement') {
        this.raise(elt.start, 'The rest element has to be the last element when destructuring');
      }
      if (elt) {
        this.toAssignable(elt, isBinding, contextDescription);
      }
    }
    return exprList as Node[];
  }

  toReferencedList(exprList: Node[]): Node[] {
    return exprList;
  }

  parseSpread(refShorthandDefaultPos: ExpPos | null): Node {
    let node = this.startNode();
    this.next();
    node.argument = this.parseMaybeAssign(false, refShorthandDefaultPos, null, null);
    return this.finishNode(node, 'SpreadElement');
  }

  parseRest(): Node {
    let node = this.startNode();
    this.next();
    node.argument = this.parseBindingIdentifier();
    return this.finishNode(node, 'RestElement');
  }

  shouldAllowYieldIdentifier(): boolean {
    return this.match(ttIns._yield) && !this.state.strict && !this.state.inGenerator;
  }

  parseBindingIdentifier(): Node {
    return this.parseIdentifier(this.shouldAllowYieldIdentifier());
  }

  parseBindingAtom(): Node | null {
    switch (this.state.type) {
      case ttIns._yield:
        if (this.state.strict || this.state.inGenerator) {
          this.unexpected(null);
        }
      case ttIns.name:
        return this.parseIdentifier(true);

      case ttIns.bracketL:
        let node = this.startNode();
        this.next();
        node.elements = this.parseBindingList(ttIns.bracketR, true) as Node[];
        return this.finishNode(node, 'ArrayPattern');

      case ttIns.braceL:
        return this.parseObj(true, null);

      default:
        this.unexpected(null);
        return null;
    }
  }

  parseBindingList(close: TokenType, allowEmpty: boolean | null): (Node | null)[] {
    let elts: (Node | null)[] = new Array();
    let first = true;
    while (!this.eat(close)) {
      if (first) {
        first = false;
      } else {
        this.expect(ttIns.comma, null);
      }
      if (allowEmpty && this.match(ttIns.comma)) {
        elts.push(null);
      } else if (this.eat(close)) {
        break;
      } else if (this.match(ttIns.ellipsis)) {
        elts.push(this.parseAssignableListItemTypes(this.parseRest()));
        this.expect(close, null);
        break;
      } else {
        let decorators: Node[] = new Array();
        while (this.match(ttIns.at)) {
          decorators.push(this.parseDecorator());
        }
        let left = this.parseMaybeDefault(null, null, null);
        if (decorators.length) {
          left.decorators = decorators;
        }
        this.parseAssignableListItemTypes(left);
        elts.push(this.parseMaybeDefault(left.start, left.loc.start, left));
      }
    }
    return elts;
  }

  parseAssignableListItemTypes(param: Node): Node {
    return param;
  }

  parseMaybeDefault(startPos: number | null, startLoc: Position | null, left: Node | null): Node {
    startLoc = startLoc !== null ? startLoc : this.state.startLoc;
    startPos = startPos !== null ? startPos : this.state.start;
    left = left || this.parseBindingAtom()!;
    if (!this.eat(ttIns.eq)) {
      return left;
    }
    let node = this.startNodeAt(startPos, startLoc);
    node.left = left;
    node.right = this.parseMaybeAssign(null, null, null, null);
    return this.finishNode(node, 'AssignmentPattern');
  }

  checkLVal(exprIn: Node, isBinding: boolean | null, checkClashesIn: Map<string, boolean> | null, contextDescription: string): void {
    let expr = exprIn;
    let checkClashes = checkClashesIn;
    switch (expr.type) {
      case 'Identifier':
        this.checkReservedWord(expr.name as string, expr.start, false, true);

        if (checkClashes) {
          let key = `_${expr.name}`;

          if (checkClashes[key]) {
            this.raise(expr.start, 'Argument name clash in strict mode');
          } else {
            checkClashes[key] = true;
          }
        }
        break;

      case 'MemberExpression':
        if (isBinding) {
          this.raise(expr.start, (isBinding ? 'Binding' : 'Assigning to') + ' member expression');
        }
        break;

      case 'ObjectPattern':
        for (let item of expr.properties!) {
          let prop = item;
          if (prop.type === 'ObjectProperty') {
            prop = prop.value as Node;
          }
          this.checkLVal(prop, isBinding, checkClashes, 'object destructuring pattern');
        }
        break;

      case 'ArrayPattern':
        for (let elem of expr.elements!) {
          let prop = elem;
          if (prop != null) {
            this.checkLVal(elem, isBinding, checkClashes, 'array destructuring pattern');
          }
        }
        break;

      case 'AssignmentPattern':
        this.checkLVal(expr.left!, isBinding, checkClashes, 'assignment pattern');
        break;

      case 'RestElement':
        this.checkLVal(expr.argument!, isBinding, checkClashes, 'rest element');
        break;

      default: {
        let message = (isBinding ? 'Binding invalid' : 'Invalid') + ' left-hand side' + (contextDescription ? ' in ' + contextDescription : 'expression');
        this.raise(expr.start, message);
      }
    }
  }

  parseTopLevel(file: Node, program: Node): Node {
    program.sourceType = this.options.sourceType;

    this.parseBlockBody(program, true, true, ttIns.eof);

    file.program = this.finishNode(program, 'Program');
    file.comments = this.state.comments;
    file.tokens = this.state.tokens;

    return this.finishNode(file, 'File');
  }

  stmtToDirective(stmt: Node): Node {
    let expr = stmt.expression as Node;

    let directiveLiteral = this.startNodeAt(expr.start, expr.loc.start);
    let directive = this.startNodeAt(stmt.start, stmt.loc.start);

    let raw = this.input.slice(expr.start, expr.end);
    let val = raw.slice(1, -1);
    directiveLiteral.value = val;

    this.addExtra(directiveLiteral, 'raw', raw);
    this.addExtra(directiveLiteral, 'rawValue', val);

    directive.value = this.finishNodeAt(directiveLiteral, 'DirectiveLiteral', expr.end, expr.loc.end!);

    return this.finishNodeAt(directive, 'Directive', stmt.end, stmt.loc.end!);
  }

  parseStatement(declaration: boolean, topLevelBool: boolean | null): Node {
    if (this.match(ttIns.at)) {
      this.parseDecorators(true);
    }
    let starttype = this.state.type;
    let node = this.startNode();
    switch (starttype) {
      case ttIns._break:
      case ttIns._continue:
        return this.parseBreakContinueStatement(node, starttype.keyword!);
      case ttIns._debugger:
        return this.parseDebuggerStatement(node);
      case ttIns._do:
        return this.parseDoStatement(node);
      case ttIns._for:
        return this.parseForStatement(node);
      case ttIns._function:
        if (!declaration) {
          this.unexpected(null);
        }
        return this.parseFunctionStatement(node);

      case ttIns._class:
        if (!declaration) {
          this.unexpected(null);
        }
        return this.parseClass(node, true, null);

      case ttIns._if:
        return this.parseIfStatement(node);
      case ttIns._return:
        return this.parseReturnStatement(node);
      case ttIns._switch:
        return this.parseSwitchStatement(node);
      case ttIns._throw:
        return this.parseThrowStatement(node);
      case ttIns._try:
        return this.parseTryStatement(node);

      case ttIns._let:
      case ttIns._const:
        if (!declaration) {
          this.unexpected(null); // NOTE: falls through to _var
        }
      case ttIns._var:
        return this.parseVarStatement(node, starttype);
      case ttIns._while:
        return this.parseWhileStatement(node);
      case ttIns._with:
        return this.parseWithStatement(node);
      case ttIns.braceL:
        return this.parseBlock(null);
      case ttIns.semi:
        return this.parseEmptyStatement(node);
      case ttIns._export:
      case ttIns._import:
        if (this.hasPlugin('dynamicImport') && this.lookahead().type === ttIns.parenL) {
          break;
        }
        if (!this.options.allowImportExportEverywhere) {
          if (!topLevelBool) {
            this.raise(this.state.start, "'import' and 'export' may only appear at the top level");
          }
          if (!this.inModule) {
            this.raise(this.state.start, "'import' and 'export' may appear only with 'sourceType: module'");
          }
        }
        return starttype === ttIns._import ? this.parseImport(node) : this.parseExport(node);
      case ttIns.name:
        if (this.state.value === 'async') {
          // peek ahead and see if next token is a function
          let state = this.state.clone(null);
          this.next();
          if (this.match(ttIns._function) && !this.canInsertSemicolon()) {
            this.expect(ttIns._function, null);
            return this.parseFunction(node, true, false, true, null);
          } else {
            this.state = state;
          }
        }
      default:
        break;
    }

    let maybeName = this.state.value as string;
    let expr = this.parseExpression(null, null);

    if (starttype === ttIns.name && expr.type === 'Identifier' && this.eat(ttIns.colon)) {
      return this.parseLabeledStatement(node, maybeName, expr);
    } else {
      return this.parseExpressionStatement(node, expr) as Node;
    }
  }

  takeDecorators(node: Node): void {
    if (this.state.decorators!.length) {
      node.decorators = this.state.decorators;
      this.state.decorators = new Array();
    }
  }

  parseDecorators(allowExport: boolean | null): void {
    while (this.match(ttIns.at)) {
      let decorator = this.parseDecorator();
      this.state.decorators!.push(decorator);
    }

    if (allowExport && this.match(ttIns._export)) {
      return;
    }

    if (!this.match(ttIns._class)) {
      this.raise(this.state.start, 'Leading decorators must be attached to a class declaration');
    }
  }

  parseDecorator(): Node {
    if (!this.hasPlugin('decorators')) {
      this.unexpected(null);
    }
    let node = this.startNode();
    this.next();
    node.expression = this.parseMaybeAssign(null, null, null, null);
    return this.finishNode(node, 'Decorator');
  }

  parseBreakContinueStatement(node: Node, keyword: string): Node {
    let isBreak = keyword === 'break';
    this.next();

    if (this.isLineTerminator()) {
      node.label = null;
    } else if (!this.match(ttIns.name)) {
      this.unexpected(null);
    } else {
      node.label = this.parseIdentifier(null);
      this.semicolon();
    }

    let i: number;
    for (i = 0; i < this.state.labels!.length; ++i) {
      let lab = this.state.labels![i];
      if (node.label == null || lab.name === node.label.name) {
        if (lab.kind != null && (isBreak || lab.kind === 'loop')) {
          break;
        }
        if (node.label && isBreak) {
          break;
        }
      }
    }
    if (i === this.state.labels!.length) {
      this.raise(node.start, 'Unsyntactic ' + keyword);
    }
    return this.finishNode(node, isBreak ? 'BreakStatement' : 'ContinueStatement');
  }

  parseDebuggerStatement(node: Node): Node {
    this.next();
    this.semicolon();
    return this.finishNode(node, 'DebuggerStatement');
  }

  parseDoStatement(node: Node): Node {
    this.next();
    this.state.labels!.push(Label.loopLabel);
    node.body = this.parseStatement(false, null);
    this.state.labels!.pop();
    this.expect(ttIns._while, null);
    node.test = this.parseParenExpression();
    this.eat(ttIns.semi);
    return this.finishNode(node, 'DoWhileStatement');
  }

  parseForStatement(node: Node): Node {
    this.next();
    this.state.labels!.push(Label.loopLabel);

    let forAwait = false;
    if (this.hasPlugin('asyncGenerators') && this.state.inAsync && this.isContextual('await')) {
      forAwait = true;
      this.next();
    }
    this.expect(ttIns.parenL, null);

    if (this.match(ttIns.semi)) {
      if (forAwait) {
        this.unexpected(null);
      }
      return this.parseFor(node, null);
    }

    if (this.match(ttIns._var) || this.match(ttIns._let) || this.match(ttIns._const)) {
      let init = this.startNode();
      let varKind = this.state.type!;
      this.next();
      this.parseVar(init, true, varKind);
      this.finishNode(init, 'VariableDeclaration');

      if (this.match(ttIns._in) || this.isContextual('of')) {
        if (init.declarations!.length === 1 && !init.declarations![0].init!) {
          return this.parseForIn(node, init, forAwait);
        }
      }
      if (forAwait) {
        this.unexpected(null);
      }
      return this.parseFor(node, init);
    }

    let refShorthandDefaultPos = new ExpPos(0);
    let init = this.parseExpression(true, refShorthandDefaultPos);
    if (this.match(ttIns._in) || this.isContextual('of')) {
      let description = this.isContextual('of') ? 'for-of statement' : 'for-in statement';
      this.toAssignable(init, null, description);
      this.checkLVal(init, null, null, description);
      return this.parseForIn(node, init, forAwait);
    } else if (refShorthandDefaultPos.start) {
      this.unexpected(refShorthandDefaultPos.start);
    }
    if (forAwait) {
      this.unexpected(null);
    }
    return this.parseFor(node, init);
  }

  parseFunctionStatement(node: Node): Node {
    this.next();
    return this.parseFunction(node, true, null, null, null);
  }

  parseIfStatement(node: Node): Node {
    this.next();
    node.test = this.parseParenExpression();
    node.consequent = this.parseStatement(false, null);
    node.alternate = this.eat(ttIns._else) ? this.parseStatement(false, null) : null;
    return this.finishNode(node, 'IfStatement');
  }

  parseReturnStatement(node: Node): Node {
    if (!this.state.inFunction && !this.options.allowReturnOutsideFunction) {
      this.raise(this.state.start, "'return' outside of function");
    }

    this.next();

    if (this.isLineTerminator()) {
      node.argument = null;
    } else {
      node.argument = this.parseExpression(null, null);
      this.semicolon();
    }

    return this.finishNode(node, 'ReturnStatement');
  }

  parseSwitchStatement(node: Node): Node {
    this.next();
    node.discriminant = this.parseParenExpression();
    node.cases = new Array();
    this.expect(ttIns.braceL, null);
    this.state.labels!.push(Label.switchLabel);

    let cur: Node | null = null;
    let sawDefault: boolean = false;
    while (!this.match(ttIns.braceR)) {
      if (this.match(ttIns._case) || this.match(ttIns._default)) {
        let isCase = this.match(ttIns._case);
        if (cur) {
          this.finishNode(cur, 'SwitchCase');
        }
        cur = this.startNode();
        node.cases.push(cur);
        cur.consequent = new Array();
        this.next();
        if (isCase) {
          cur.test = this.parseExpression(null, null);
        } else {
          if (sawDefault) {
            this.raise(this.state.lastTokStart, 'Multiple default clauses');
          }
          sawDefault = true;
          cur.test = null;
        }
        this.expect(ttIns.colon, null);
      } else {
        if (cur !== null) {
          let arr = new Array<Node>();
          arr.push(this.parseStatement(true, null));
          let arr1 = cur.consequent as Node[];
          arr1 = arr1.concat(arr);
          cur.consequent = arr1;
        } else {
          this.unexpected(null);
        }
      }
    }
    if (cur) {
      this.finishNode(cur, 'SwitchCase');
    }
    this.next(); // Closing brace
    this.state.labels!.pop();
    return this.finishNode(node, 'SwitchStatement');
  }

  parseThrowStatement(node: Node): Node {
    this.next();
    if (lineBreak.test(this.input.slice(this.state.lastTokEnd, this.state.start))) {
      this.raise(this.state.lastTokEnd, 'Illegal newline after throw');
    }
    node.argument = this.parseExpression(null, null);
    this.semicolon();
    return this.finishNode(node, 'ThrowStatement');
  }

  parseTryStatement(node: Node): Node {
    this.next();

    node.block = this.parseBlock(null);
    node.handler = null;

    if (this.match(ttIns._catch)) {
      let clause = this.startNode();
      this.next();

      this.expect(ttIns.parenL, null);
      clause.param = this.parseBindingAtom();
      this.checkLVal(clause.param!, true, new Map<string, boolean>(), 'catch clause');
      this.expect(ttIns.parenR, null);

      clause.body = this.parseBlock(null);
      node.handler = this.finishNode(clause, 'CatchClause');
    }

    node.guardedHandlers = empty;
    node.finalizer = this.eat(ttIns._finally) ? this.parseBlock(null) : null;

    if (!node.handler && !node.finalizer) {
      this.raise(node.start, 'Missing catch or finally clause');
    }

    return this.finishNode(node, 'TryStatement');
  }

  parseVarStatement(node: Node, kind: string | TokenType): Node {
    this.next();
    this.parseVar(node, false, kind);
    this.semicolon();
    return this.finishNode(node, 'VariableDeclaration');
  }

  parseWhileStatement(node: Node): Node {
    this.next();
    node.test = this.parseParenExpression();
    this.state.labels!.push(Label.loopLabel);
    node.body = this.parseStatement(false, null);
    this.state.labels!.pop();
    return this.finishNode(node, 'WhileStatement');
  }

  parseWithStatement(node: Node): Node {
    if (this.state.strict) {
      this.raise(this.state.start, "'with' in strict mode");
    }
    this.next();
    node.object = this.parseParenExpression();
    node.body = this.parseStatement(false, null);
    return this.finishNode(node, 'WithStatement');
  }

  parseEmptyStatement(node: Node): Node {
    this.next();
    return this.finishNode(node, 'EmptyStatement');
  }

  parseLabeledStatement(node: Node, maybeName: string, expr: Node): Node {
    for (let label of this.state.labels!) {
      let name = '';
      if (label instanceof Label) {
        name = label.name!;
      }
      if (name === maybeName) {
        this.raise(expr.start, `Label '${maybeName}' is already declared`);
      }
    }

    let kind = this.state.type!.isLoop ? 'loop' : this.match(ttIns._switch) ? 'switch' : null;
    let i = this.state.labels!.length - 1;
    while (i >= 0) {
      let label = this.state.labels![i];
      if (label.statementStart === node.start) {
        label.statementStart = this.state.start;
        label.kind = kind;
      } else {
        break;
      }
      i -= 1;
    }

    let lab = Label.init(kind!, maybeName, this.state.start);
    this.state.labels!.push(lab);
    node.body = this.parseStatement(true, null);
    this.state.labels!.pop();
    node.label = expr;
    return this.finishNode(node, 'LabeledStatement');
  }

  parseExpressionStatement(node: Node, expr: Node): Node | null {
    node.expression = expr;
    this.semicolon();
    return this.finishNode(node, 'ExpressionStatement');
  }

  parseBlock(allowDirectives: boolean | null): Node {
    let node = this.startNode();
    this.expect(ttIns.braceL, null);
    this.parseBlockBody(node, allowDirectives, false, ttIns.braceR);
    return this.finishNode(node, 'BlockStatement');
  }

  isValidDirective(stmt: Node): boolean {
    return (
      stmt.type === 'ExpressionStatement' &&
        (stmt.expression! as Node).type! === 'StringLiteral' &&
        (stmt.expression! as Node).extra!.get('parenthesized') === undefined
    );
  }

  parseBlockBody(node: Node, allowDirectives: boolean | null, topLevel: boolean | null, end: TokenType | null): void {
    node.body = new Array();
    node.directives = new Array();

    let parsedNonDirective = false;
    let oldStrict: boolean | null = null;
    let octalPosition: number | null = null;

    while (!this.eat(end!)) {
      if (!parsedNonDirective && this.state.containsOctal && !octalPosition) {
        octalPosition = this.state.octalPosition!;
      }

      let stmt = this.parseStatement(true, topLevel);

      if (allowDirectives && !parsedNonDirective && this.isValidDirective(stmt)) {
        let directive = this.stmtToDirective(stmt);
        node.directives.push(directive);

        if (oldStrict === null && (directive.value as Node).value === 'use strict') {
          oldStrict = this.state.strict;
          this.setStrict(true);

          if (octalPosition) {
            this.raise(octalPosition, 'Octal literal in strict mode');
          }
        }

        continue;
      }

      parsedNonDirective = true;
      let arr = node.body as Node[];
      arr.push(stmt);
      node.body = arr;
    }

    if (oldStrict === false) {
      this.setStrict(false);
    }
  }

  parseFor(node: Node, init: Node | null): Node {
    node.init = init;
    this.expect(ttIns.semi, null);
    node.test = this.match(ttIns.semi) ? null : this.parseExpression(null, null);
    this.expect(ttIns.semi, null);
    node.update = this.match(ttIns.parenR) ? null : this.parseExpression(null, null);
    this.expect(ttIns.parenR, null);
    node.body = this.parseStatement(false, null);
    this.state.labels!.pop();
    return this.finishNode(node, 'ForStatement');
  }

  parseForIn(node: Node, init: Node, forAwait: boolean): Node {
    let type = this.match(ttIns._in) ? 'ForInStatement' : 'ForOfStatement';
    if (forAwait) {
      this.eatContextual('of');
    } else {
      this.next();
    }
    node.await = forAwait;
    node.left = init;
    node.right = this.parseExpression(null, null);
    this.expect(ttIns.parenR, null);
    node.body = this.parseStatement(false, null);
    this.state.labels!.pop();
    return this.finishNode(node, type);
  }

  parseVar(node: Node, isFor: boolean, kind: TokenType | string): Node {
    node.declarations = new Array();
    node.kind = (kind as TokenType).keyword;
    while (true) {
      let decl = this.startNode();
      this.parseVarHead(decl);
      if (this.eat(ttIns.eq)) {
        decl.init = this.parseMaybeAssign(isFor, null, null, null);
      } else if (kind === ttIns._const && !(this.match(ttIns._in) || this.isContextual('of'))) {
        this.unexpected(null);
      } else if (decl.id!.type !== 'Identifier' && !(isFor && (this.match(ttIns._in) || this.isContextual('of')))) {
        this.raise(this.state.lastTokEnd, 'Complex binding patterns require an initialization value');
      } else {
        decl.init = null;
      }
      node.declarations.push(this.finishNode(decl, 'VariableDeclarator'));
      if (!this.eat(ttIns.comma)) {
        break;
      }
    }
    return node;
  }

  parseVarHead(decl: Node): void {
    decl.id = this.parseBindingAtom();
    this.checkLVal(decl.id!, true, null, 'variable declaration');
  }

  parseFunction(node: Node, isStatement: boolean, allowExpressionBody: boolean | null, isAsync: boolean | null, optionalId: boolean | null): Node {
    let oldInMethod = this.state.inMethod;
    this.state.inMethod = false;

    this.initFunction(node, isAsync);

    if (this.match(ttIns.star)) {
      if (node.async && !this.hasPlugin('asyncGenerators')) {
        this.unexpected(null);
      } else {
        node.generator = true;
        this.next();
      }
    }

    if (isStatement && !optionalId && !this.match(ttIns.name) && !this.match(ttIns._yield)) {
      this.unexpected(null);
    }

    if (this.match(ttIns.name) || this.match(ttIns._yield)) {
      node.id = this.parseBindingIdentifier();
    }
    this.parseFunctionParams(node);
    this.parseFunctionBody(node, allowExpressionBody);
    this.state.inMethod = oldInMethod;
    return this.finishNode(node, isStatement ? 'FunctionDeclaration' : 'FunctionExpression');
  }

  parseFunctionParams(node: Node): void {
    this.expect(ttIns.parenL, null);
    node.params = this.parseBindingList(ttIns.parenR, null);
  }

  parseClass(node: Node, isStatement: boolean, optionalId: boolean | null): Node {
    this.next();
    this.takeDecorators(node);
    this.parseClassId(node, isStatement, optionalId);
    this.parseClassSuper(node, null);
    this.parseClassBody(node);
    return this.finishNode(node, isStatement ? 'ClassDeclaration' : 'ClassExpression');
  }

  isClassProperty(): boolean {
    return this.match(ttIns.eq) || this.match(ttIns.semi) || this.match(ttIns.braceR);
  }

  isClassMethod(): boolean {
    return this.match(ttIns.parenL);
  }

  isNonstaticConstructor(method: Node): boolean {
    return (
      !method.computed &&
        !method.static &&
        (method.key!.name! === 'constructor' || // Identifier
          method.key!.value! === 'constructor') // Literal
    );
  }

  parseClassBody(node: Node): void {
    let oldStrict = this.state.strict;
    this.state.strict = true;
    let hadConstructor = false;
    let decorators: Node[] = new Array();
    let classBody = this.startNode();
    classBody.body = new Array();
    this.expect(ttIns.braceL, null);

    while (!this.eat(ttIns.braceR)) {
      if (this.eat(ttIns.semi)) {
        if (decorators.length > 0) {
          this.raise(this.state.lastTokEnd, 'Decorators must not be followed by a semicolon');
        }
        continue;
      }
      if (this.match(ttIns.at)) {
        decorators.push(this.parseDecorator());
        continue;
      }
      let method = this.startNode();

      if (decorators.length) {
        method.decorators = decorators;
        decorators = new Array();
      }

      method.static = false;
      if (this.match(ttIns.name) && this.state.value === 'static') {
        let key = this.parseIdentifier(true); // eats 'static'
        if (this.isClassMethod()) {
          method.kind = 'method';
          method.computed = false;
          method.key = key;
          this.parseClassMethod(classBody, method, false, false);
          continue;
        } else if (this.isClassProperty()) {
          method.computed = false;
          method.key = key;
          let arr = classBody.body as Node[];
          arr.push(this.parseClassProperty(method));
          classBody.body = arr;
          continue;
        }
        method.static = true;
      }

      if (this.eat(ttIns.star)) {
        method.kind = 'method';
        this.parsePropertyName(method);
        if (this.isNonstaticConstructor(method)) {
          this.raise(method.key!.start!, "Constructor can't be a generator");
        }
        if (!method.computed! && method.static && (method.key!.name! === 'prototype' || method.key!.value! === 'prototype')) {
          this.raise(method.key!.start!, 'Classes may not have static property named prototype');
        }
        this.parseClassMethod(classBody, method, true, false);
      } else {
        let isSimple = this.match(ttIns.name);
        let key = this.parsePropertyName(method);
        if (!method.computed && method.static && (method.key!.name! === 'prototype' || method.key!.value! === 'prototype')) {
          this.raise(method.key!.start!, 'Classes may not have static property named prototype');
        }
        if (this.isClassMethod()) {
          if (this.isNonstaticConstructor(method)) {
            if (hadConstructor) {
              this.raise(key.start, 'Duplicate constructor in the same class');
            } else if (method.decorators) {
              this.raise(method.start, "You can't attach decorators to a class constructor");
            }
            hadConstructor = true;
            method.kind = 'constructor';
          } else {
            method.kind = 'method';
          }
          this.parseClassMethod(classBody, method, false, false);
        } else if (this.isClassProperty()) {
          if (this.isNonstaticConstructor(method)) {
            this.raise(method.key!.start!, "Classes may not have a non-static field named 'constructor'");
          }
          let arr = classBody.body;
          arr.push(this.parseClassProperty(method));
          classBody.body = arr;
        } else if (isSimple && key.name === 'async' && !this.isLineTerminator()) {
          let isGenerator = this.hasPlugin('asyncGenerators') && this.eat(ttIns.star);
          method.kind = 'method';
          this.parsePropertyName(method);
          if (this.isNonstaticConstructor(method)) {
            this.raise(method.key!.start!, "Constructor can't be an async function");
          }
          this.parseClassMethod(classBody, method, isGenerator, true);
        } else if (isSimple && (key.name === 'get' || key.name === 'set') && !(this.isLineTerminator() && this.match(ttIns.star))) {
          // `get\n*` is an uninitialized property named 'get' followed by a generator.
          method.kind = key.name;
          this.parsePropertyName(method);
          if (this.isNonstaticConstructor(method)) {
            this.raise(method.key!.start!, "Constructor can't have get/set modifier");
          }
          this.parseClassMethod(classBody, method, false, false);
          this.checkGetterSetterParamCount(method);
        } else if (this.isLineTerminator()) {
          if (this.isNonstaticConstructor(method)) {
            this.raise(method.key!.start, "Classes may not have a non-static field named 'constructor'");
          }
          let arr = classBody.body as Node[];
          arr.push(this.parseClassProperty(method));
          classBody.body = arr;
        } else {
          this.unexpected(null);
        }
      }
    }
    if (decorators.length) {
      this.raise(this.state.start, 'You have trailing decorators with no method');
    }
    node.body = this.finishNode(classBody, 'ClassBody');
    this.state.strict = oldStrict;
  }

  parseClassProperty(node: Node): Node {
    let noPluginMsg = "You can only use Class Properties when the 'classProperties' plugin is enabled.";
    if (!node.typeAnnotation && !this.hasPlugin('classProperties')) {
      this.raise(node.start, noPluginMsg);
    }

    if (this.match(ttIns.eq)) {
      if (!this.hasPlugin('classProperties')) {
        this.raise(this.state.start, noPluginMsg);
      }
      this.next();
      node.value = this.parseMaybeAssign(null, null, null, null);
    } else {
      node.value = null;
    }
    this.semicolon();
    return this.finishNode(node, 'ClassProperty');
  }

  parseClassMethod(classBody: Node, method: Node, isGenerator: boolean, isAsync: boolean): void {
    this.parseMethod(method, isGenerator, isAsync);
    let arr = classBody.body as Node[];
    arr.push(this.finishNode(method, 'ClassMethod'));
    classBody.body = arr;
  }

  parseClassId(node: Node, isStatement: boolean, optionalId: boolean | null): void {
    if (this.match(ttIns.name)) {
      node.id = this.parseIdentifier(null);
    } else {
      if (optionalId || !isStatement) {
        node.id = null;
      } else {
        this.unexpected(null);
      }
    }
  }

  parseClassSuper(node: Node, isStatement: boolean | null): void {
    node.superClass = this.eat(ttIns._extends) ? this.parseExprSubscripts(null) : null;
  }

  parseExport(node: Node): Node {
    this.next();
    if (this.match(ttIns.star)) {
      let specifier = this.startNode();
      this.next();
      if (this.hasPlugin('exportExtensions') && this.eatContextual('as')) {
        specifier.exported = this.parseIdentifier(null);
        node.specifiers = [this.finishNode(specifier, 'ExportNamespaceSpecifier')];
        this.parseExportSpecifiersMaybe(node);
        this.parseExportFrom(node, true);
      } else {
        this.parseExportFrom(node, true);
        return this.finishNode(node, 'ExportAllDeclaration');
      }
    } else if (this.hasPlugin('exportExtensions') && this.isExportDefaultSpecifier()) {
      let specifier = this.startNode();
      specifier.exported = this.parseIdentifier(true);
      node.specifiers = [this.finishNode(specifier, 'ExportDefaultSpecifier')];
      if (this.match(ttIns.comma) && this.lookahead().type === ttIns.star) {
        this.expect(ttIns.comma, null);
        let specifier = this.startNode();
        this.expect(ttIns.star, null);
        this.expectContextual('as', null);
        specifier.exported = this.parseIdentifier(null);
        node.specifiers.push(this.finishNode(specifier, 'ExportNamespaceSpecifier'));
      } else {
        this.parseExportSpecifiersMaybe(node);
      }
      this.parseExportFrom(node, true);
    } else if (this.eat(ttIns._default)) {
      // export default ...
      let expr = this.startNode();
      let needsSemi = false;
      if (this.eat(ttIns._function)) {
        expr = this.parseFunction(expr, true, false, false, true);
      } else if (this.isContextual('async') && this.lookahead().type === ttIns._function) {
        // async function declaration
        this.eatContextual('async');
        this.eat(ttIns._function);
        expr = this.parseFunction(expr, true, false, true, true);
      } else if (this.match(ttIns._class)) {
        expr = this.parseClass(expr, true, true);
      } else {
        needsSemi = true;
        expr = this.parseMaybeAssign(null, null, null, null);
      }
      node.declaration = expr;
      if (needsSemi) {
        this.semicolon();
      }
      this.checkExport(node, true, true);
      return this.finishNode(node, 'ExportDefaultDeclaration');
    } else if (this.shouldParseExportDeclaration()) {
      node.specifiers = new Array();
      node.source = null;
      node.declaration = this.parseExportDeclaration(null);
    } else {
      // export { x, y as z } [from '...']
      node.declaration = null;
      node.specifiers = this.parseExportSpecifiers();
      this.parseExportFrom(node, null);
    }
    this.checkExport(node, true, null);
    return this.finishNode(node, 'ExportNamedDeclaration');
  }

  parseExportDeclaration(node: Node | null): Node | null {
    return this.parseStatement(true, null);
  }

  isExportDefaultSpecifier(): boolean {
    if (this.match(ttIns.name)) {
      return this.state.value !== 'type' && this.state.value !== 'async' && this.state.value !== 'interface';
    }

    if (!this.match(ttIns._default)) {
      return false;
    }

    let lookahead = this.lookahead();
    return lookahead.type === ttIns.comma || (lookahead.type === ttIns.name && lookahead.value === 'from');
  }

  parseExportSpecifiersMaybe(node: Node): void {
    if (this.eat(ttIns.comma)) {
      node.specifiers = node.specifiers!.concat(this.parseExportSpecifiers());
    }
  }

  parseExportFrom(node: Node, expect: boolean | null): void {
    if (this.eatContextual('from')) {
      node.source = this.match(ttIns.string) ? this.parseExprAtom(null) : this.unexpected(null);
      this.checkExport(node, null, null);
    } else {
      if (expect) {
        this.unexpected(null);
      } else {
        node.source = null;
      }
    }
    this.semicolon();
  }

  shouldParseExportDeclaration(): boolean {
    return (
      this.state.type!.keyword! === 'var' ||
        this.state.type!.keyword! === 'const' ||
        this.state.type!.keyword! === 'let' ||
        this.state.type!.keyword! === 'function' ||
        this.state.type!.keyword! === 'class' ||
      this.isContextual('async')
    );
  }

  checkExport(node: Node, checkNames: boolean | null, isDefault: boolean | null): void {
    if (checkNames) {
      let isDefaultBool = isDefault !== null ? isDefault : false;
      if (isDefaultBool) {
        this.checkDuplicateExports(node, 'default');
      } else if (node.specifiers && node.specifiers.length) {
        for (let specifier of node.specifiers) {
          this.checkDuplicateExports(specifier, specifier.exported!.name! as string);
        }
      } else if (node.declaration) {
        if (node.declaration.type === 'FunctionDeclaration' || node.declaration.type === 'ClassDeclaration') {
          this.checkDuplicateExports(node, node.declaration!.id!.name! as string);
        } else if (node.declaration.type === 'VariableDeclaration') {
          for (let declaration of node.declaration.declarations!) {
            this.checkDeclaration(declaration.id!);
          }
        }
      }
    }

    if (this.state.decorators!.length) {
      let isClass = node.declaration && (node.declaration.type === 'ClassDeclaration' || node.declaration.type === 'ClassExpression');
      if (!node.declaration || !isClass) {
        this.raise(node.start, 'You can only use decorators on an export when exporting a class');
      }
      this.takeDecorators(node.declaration!);
    }
  }

  checkDeclaration(node: Node): void {
    if (node.type === 'ObjectPattern') {
      for (let prop of node.properties!) {
        this.checkDeclaration(prop);
      }
    } else if (node.type === 'ArrayPattern') {
      for (let elem of node.elements!) {
        if (elem !== null) {
          this.checkDeclaration(elem);
        }
      }
    } else if (node.type === 'ObjectProperty') {
      this.checkDeclaration(node.value as Node);
    } else if (node.type === 'RestElement') {
      this.checkDeclaration(node.argument!);
    } else if (node.type === 'Identifier') {
      this.checkDuplicateExports(node, node.name as string);
    }
  }

  checkDuplicateExports(node: Node, name: string): void {
    if (this.state.exportedIdentifiers!.indexOf(name) > -1) {
      this.raiseDuplicateExportError(node, name);
    }
    this.state.exportedIdentifiers!.push(name);
  }

  raiseDuplicateExportError(node: Node, name: string): void {
    let message =
      name === 'default' ? 'Only one default export allowed per module.' : `\`${name}\` has already been exported. Exported identifiers must be unique.`;
    this.raise(node.start, message);
  }

  parseExportSpecifiers(): Node[] {
    let nodes: Node[] = new Array();
    let first = true;
    let needsFrom: boolean | null = null;

    this.expect(ttIns.braceL, null);

    while (!this.eat(ttIns.braceR)) {
      if (first) {
        first = false;
      } else {
        this.expect(ttIns.comma, null);
        if (this.eat(ttIns.braceR)) {
          break;
        }
      }

      let isDefault = this.match(ttIns._default);
      if (isDefault && !needsFrom) {
        needsFrom = true;
      }

      let node = this.startNode();
      node.local = this.parseIdentifier(isDefault);
      node.exported = this.eatContextual('as') ? this.parseIdentifier(true) : node.local.clone();
      nodes.push(this.finishNode(node, 'ExportSpecifier'));
    }

    if (needsFrom != null && needsFrom && !this.isContextual('from')) {
      this.unexpected(null);
    }
    return nodes;
  }

  parseImport(node: Node): Node {
    this.eat(ttIns._import);

    if (this.match(ttIns.string)) {
      node.specifiers = new Array();
      node.source = this.parseExprAtom(null);
    } else {
      node.specifiers = new Array();
      this.parseImportSpecifiers(node);
      this.expectContextual('from', null);
      node.source = this.match(ttIns.string) ? this.parseExprAtom(null) : this.unexpected(null);
    }
    this.semicolon();
    return this.finishNode(node, 'ImportDeclaration');
  }

  parseImportSpecifiers(node: Node): void {
    let first = true;
    if (this.match(ttIns.name)) {
      let startPos = this.state.start;
      let startLoc = this.state.startLoc!;
      node.specifiers!.push(this.parseImportSpecifierDefault(this.parseIdentifier(null), startPos, startLoc));
      if (!this.eat(ttIns.comma)) {
        return;
      }
    }

    if (this.match(ttIns.star)) {
      let specifier = this.startNode();
      this.next();
      this.expectContextual('as', null);
      specifier.local = this.parseIdentifier(null);
      this.checkLVal(specifier.local, true, null, 'import namespace specifier');
      node.specifiers!.push(this.finishNode(specifier, 'ImportNamespaceSpecifier'));
      return;
    }

    this.expect(ttIns.braceL, null);
    while (!this.eat(ttIns.braceR)) {
      if (first) {
        first = false;
      } else {
        if (this.eat(ttIns.colon)) {
          this.unexpected(null, 'ES2015 named imports do not destructure. Use another statement for destructuring after the import.');
        }

        this.expect(ttIns.comma, null);
        if (this.eat(ttIns.braceR)) {
          break;
        }
      }
      this.parseImportSpecifier(node);
    }
  }

  parseImportSpecifier(node: Node): void {
    let specifier = this.startNode();
    specifier.imported = this.parseIdentifier(true);
    if (this.eatContextual('as')) {
      specifier.local = this.parseIdentifier(null);
    } else {
      this.checkReservedWord(specifier.imported.name as string, specifier.start, true, true);
      specifier.local = specifier.imported.clone();
    }
    this.checkLVal(specifier.local, true, null, 'import specifier');
    node.specifiers!.push(this.finishNode(specifier, 'ImportSpecifier'));
  }

  parseImportSpecifierDefault(id: Node, startPos: number, startLoc: Position): Node {
    let node = this.startNodeAt(startPos, startLoc);
    node.local = id;
    this.checkLVal(node.local, true, null, 'default import specifier');
    return this.finishNode(node, 'ImportDefaultSpecifier');
  }
}

function numberValue(of: string): number {
  if (of <= 'F' && of >= 'A') {
    return of.charCodeAt(0) - INT_VAL_P_55;
  }
  if (of <= 'f' && of >= 'a') {
    return of.charCodeAt(0) - INT_VAL_P_87;
  }
  return of.charCodeAt(0) - INT_VAL_P_48;
}

function parseInt1(str: string, redix: number): number {
  return str.length === 1 ? numberValue(str[str.length - 1]) : parseInt1(str.substring(0, str.length - 1), redix) * redix + numberValue(str[str.length - 1]);
}

function fixStringValue(value: number | string): number {
  if (typeof value === 'number') {
    return value as number;
  } else {
    return parseInt1(value as string, INT_VAL_P_10);
  }
}

function isSimpleProperty(node: Node | null): boolean {
  return node !== null && node.type === 'Property' && node.kind === 'init' && node.method === false;
}

class EsTreeParser extends Parser {
  estreeParseRegExpLiteral(config: RegConfig): Node {
    let pattern = config.pattern;
    let flags = config.flags;
    let regex: RegExp | null = null;
    try {
      regex = new RegExp(pattern, flags);
    } catch (e) {}
    let node = this.estreeParseLiteral(config);
    node.regex = config;
    return node;
  }

  estreeParseLiteral(value: string | number | RegConfig | boolean | null): Node {
    return this.parseLiteral(value, 'Literal', null, null);
  }

  directiveToStmt(directive: Node): Node {
    let directiveLiteral = directive.value as Node;

    let stmt = this.startNodeAt(directive.start, directive.loc.start);
    let expression = this.startNodeAt(directiveLiteral.start, directiveLiteral.loc.start);

    expression.value = directiveLiteral.value;
    expression.raw = directiveLiteral.extra!.get('raw') as string;

    stmt.expression = this.finishNodeAt(expression, 'Literal', directiveLiteral.end, directiveLiteral.loc.end!);
    stmt.directive = (directiveLiteral.extra!.get('raw') as string).slice(1, -1);

    return this.finishNodeAt(stmt, 'ExpressionStatement', directive.end, directive.loc.end!);
  }

  checkDeclaration(node: Node | null): void {
    if (isSimpleProperty(node)) {
      this.checkDeclaration(node!.value! as Node);
    } else {
      super.checkDeclaration(node!);
    }
  }

  checkGetterSetterParamCount(prop: Node): void {
    let paramCount = prop.kind === 'get' ? 0 : 1;
    if ((prop.value as Node).params!.length !== paramCount) {
      let start = prop.start;
      if (prop.kind === 'get') {
        this.raise(start, 'getter should have no params');
      } else {
        this.raise(start, 'setter should have exactly one param');
      }
    }
  }

  checkLVal(expr: Node, isBinding: boolean | null, checkClashes: Map<string, boolean> | null, contextDescription: string): void {
    switch (expr.type) {
      case 'ObjectPattern':
        expr.properties!.forEach(prop => {
          this.checkLVal(prop.type === 'Property' ? (prop.value as Node) : prop, isBinding, checkClashes, 'object destructuring pattern');
        });
        break;
      default:
        super.checkLVal(expr, isBinding, checkClashes, contextDescription);
    }
  }

  checkPropClash(prop: Node, propHash: Object): void {
    if (prop.computed || !isSimpleProperty(prop)) {
      return;
    }
  }

  isStrictBody(node: Node, isExpression: boolean): boolean {
    if (!isExpression && ((node.body as Node).body as Node[]).length > 0) {
      for (let directive of (node.body as Node).body as Node[]) {
        if (directive.type === 'ExpressionStatement' && (directive.expression as Node).type === 'Literal') {
          if ((directive.expression as Node).value === 'use strict') {
            return true;
          }
        } else {
          break;
        }
      }
    }

    return false;
  }

  isValidDirective(stmt: Node): boolean {
    return (
      stmt.type === 'ExpressionStatement' &&
        (stmt.expression as Node).type === 'Literal' &&
        typeof (stmt.expression as Node).value === 'string' &&
        (!(stmt.expression as Node).extra || !(stmt.expression as Node).extra!.get('parenthesized'))
    );
  }

  parseBlockBody(node: Node, allowDirectives: boolean, topLevel: boolean, end: TokenType): void {
    super.parseBlockBody(node, allowDirectives, topLevel, end);
    node.directives!.reverse().forEach(directive => {
      let arr = node.body as Node[];
      arr.unshift(this.directiveToStmt(directive));
      node.body = arr;
    });
    node.directives = null;
  }

  parseClassMethod(classBody: Node, method: Node, isGenerator: boolean, isAsync: boolean): void {
    super.parseClassMethod(classBody, method, isGenerator, isAsync);
    let body = classBody.body as Node[];
    body[body.length - 1].type = 'MethodDefinition';
  }

  parseExprAtom(refShorthandDefaultPos: ExpPos | null): Node {
    switch (this.state.type) {
      case ttIns.regexp:
        return this.estreeParseRegExpLiteral(this.state.value as RegConfig);
      case ttIns.num:
      case ttIns.string:
        return this.estreeParseLiteral(this.state.value);

      case ttIns._null:
        return this.estreeParseLiteral(null);

      case ttIns._true:
        return this.estreeParseLiteral(true);

      case ttIns._false:
        return this.estreeParseLiteral(false);

      default:
        return super.parseExprAtom(refShorthandDefaultPos) as Node;
    }
  }

  parseLiteral(value: string | number | RegConfig | boolean | null, type: string, startPos: number | null, startLoc: Position | null): Node {
    let node = super.parseLiteral(value, type, startPos, startLoc);
    node.raw = node.extra!.get('raw') as string;
    node.extra = null;
    return node;
  }

  parseMethod(node: Node, isGenerator: boolean | null, isAsync: boolean | null): Node {
    let funcNode = this.startNode();
    funcNode.kind = node.kind; // provide kind, so inner method correctly sets state
    funcNode = super.parseMethod(node, isGenerator, isAsync);
    funcNode.kind = null;
    node.value = this.finishNode(funcNode, 'FunctionExpression');

    return node;
  }

  parseObjectMethod(prop: Node, isGenerator: boolean, isAsync: boolean, isPattern: boolean): Node {
    let node = super.parseObjectMethod(prop, isGenerator, isAsync, isPattern);
    if (node !== null) {
      if (node.kind === 'method') {
        node.kind = 'init';
      }
      node.type = 'Property';
    }
    return node as Node;
  }

  parseObjectProperty(prop: Node, startPos: number, startLoc: Position, isPattern: boolean, refShorthandDefaultPos: ExpPos): Node | null {
    let node = super.parseObjectProperty(prop, startPos, startLoc, isPattern, refShorthandDefaultPos);
    if (node) {
      node.kind = 'init';
      node.type = 'Property';
    }
    return node;
  }

  toAssignable(node: Node, isBinding: boolean | null, contextDescription: string): Node {
    if (isSimpleProperty(node)) {
      this.toAssignable(node.value as Node, isBinding, contextDescription);

      return node;
    } else if (node.type === 'ObjectExpression') {
      node.type = 'ObjectPattern';
      for (let prop of node.properties!) {
        if (prop.kind === 'get' || prop.kind === 'set') {
          this.raise(prop.key!.start, "Object pattern can't contain getter or setter");
        } else if (prop.method) {
          this.raise(prop.key!.start, "Object pattern can't contain methods");
        } else {
          this.toAssignable(prop, isBinding, 'object destructuring pattern');
        }
      }

      return node;
    }
    return super.toAssignable(node, isBinding, contextDescription);
  }
}

class JsxParser extends EsTreeParser {
  jsxReadToken(): void {
    let outs = '';
    let chunkStart = this.state.pos;
    while (true) {
      if (this.state.pos >= this.input.length) {
        this.raise(this.state.start, 'Unterminated JSX contents');
      }

      let ch = this.input.charCodeAt(this.state.pos);

      switch (ch) {
        case INT_VAL_P_60: // "<"
        case INT_VAL_P_123: // "{"
          if (this.state.pos === this.state.start) {
            if (ch === INT_VAL_P_60 && this.state.exprAllowed) {
              this.state.pos += 1;
              return this.finishToken(ttIns.jsxTagStart, null);
            }
            return this.getTokenFromCode(ch);
          }
          outs += this.input.slice(chunkStart, this.state.pos);
          return this.finishToken(ttIns.jsxText, outs);

        case INT_VAL_P_38:
          outs += this.input.slice(chunkStart, this.state.pos);
          outs += this.jsxReadEntity();
          chunkStart = this.state.pos;
          break;

        default:
          if (isNewLine(ch)) {
            outs += this.input.slice(chunkStart, this.state.pos);
            outs += this.jsxReadNewLine(true);
            chunkStart = this.state.pos;
          } else {
            this.state.pos += 1;
          }
      }
    }
  }
  jsxReadNewLine(normalizeCRLF: boolean): string {
    let ch = this.input.charCodeAt(this.state.pos);
    let out: string;
    this.state.pos += 1;
    if (ch === INT_VAL_P_13 && this.input.charCodeAt(this.state.pos) === INT_VAL_P_10) {
      this.state.pos += 1;
      out = normalizeCRLF ? '\n' : '\r\n';
    } else {
      out = String.fromCharCode(ch);
    }
    this.state.curLine += 1;
    this.state.lineStart = this.state.pos;

    return out;
  }
  jsxReadString(quote: number): void {
    let outs = '';
    this.state.pos += 1;
    let chunkStart = this.state.pos;
    while (true) {
      if (this.state.pos >= this.input.length) {
        this.raise(this.state.start, 'Unterminated string constant');
      }

      let ch = this.input.charCodeAt(this.state.pos);
      if (ch === quote) {
        break;
      }
      if (ch === INT_VAL_P_38) {
        // "&"
        outs += this.input.slice(chunkStart, this.state.pos);
        outs += this.jsxReadEntity();
        chunkStart = this.state.pos;
      } else if (isNewLine(ch)) {
        outs += this.input.slice(chunkStart, this.state.pos);
        outs += this.jsxReadNewLine(false);
        chunkStart = this.state.pos;
      } else {
        this.state.pos += 1;
      }
    }
    outs += this.input.slice(chunkStart, this.state.pos++);
    return this.finishToken(ttIns.string, outs);
  }
  jsxReadEntity(): string {
    let str = '';
    let count = 0;
    let entity: string | null = null;
    let ch = this.input[this.state.pos];
    this.state.pos += 1;
    let startPos = this.state.pos;
    while (this.state.pos < this.input.length && count < INT_VAL_P_10) {
      count += 1;
      ch = this.input[this.state.pos];
      this.state.pos += 1;
      if (ch === ';') {
        if (str[0] === '#') {
          if (str[1] === 'x') {
            str = str.substr(INT_VAL_P_2);
            if (HEX_NUMBER.test(str)) {
              entity = String.fromCodePoint(parseInt1(str, INT_VAL_P_16));
            }
          } else {
            str = str.substr(1);
            if (DECIMAL_NUMBER.test(str)) {
              entity = String.fromCodePoint(parseInt1(str, INT_VAL_P_10));
            }
          }
        } else {
          entity = XHTMLEntities[str];
        }
        break;
      }
      str += ch;
    }
    if (entity === null) {
      this.state.pos = startPos;
      return '&';
    }
    return entity;
  }
  jsxReadWord(): void {
    let ch: number;
    let start = this.state.pos;
    do {
      this.state.pos += 1;
      ch = this.input.charCodeAt(this.state.pos);
    } while (isIdentifierChar(ch) || ch === INT_VAL_P_45); // "-"
    return this.finishToken(ttIns.jsxName, this.input.slice(start, this.state.pos));
  }
  jsxParseIdentifier(): Node {
    let node = this.startNode();
    if (this.match(ttIns.jsxName)) {
      node.name = this.state.value as string;
    } else if (this.state.type!.keyword !== null) {
      node.name = this.state.type!.keyword;
    } else {
      this.unexpected(null);
    }
    this.next();
    return this.finishNode(node, 'JSXIdentifier');
  }
  jsxParseNamespacedName(): Node {
    let startPos = this.state.start;
    let startLoc = this.state.startLoc;
    let name = this.jsxParseIdentifier();
    if (!this.eat(ttIns.colon)) {
      return name;
    }

    let node = this.startNodeAt(startPos, startLoc);
    node.namespace = name;
    node.name = this.jsxParseIdentifier();
    return this.finishNode(node, 'JSXNamespacedName');
  }
  jsxParseElementName(): Node {
    let startPos = this.state.start;
    let startLoc = this.state.startLoc;
    let node = this.jsxParseNamespacedName();
    while (this.eat(ttIns.dot)) {
      let newNode = this.startNodeAt(startPos, startLoc);
      newNode.object = node;
      newNode.property = this.jsxParseIdentifier();
      node = this.finishNode(newNode, 'JSXMemberExpression');
    }
    return node;
  }
  jsxParseAttributeValue(): Node | null {
    let node: Node | null = null;
    switch (this.state.type) {
      case ttIns.braceL:
        node = this.jsxParseExpressionContainer();
        if ((node.expression as Node).type === 'JSXEmptyExpression') {
          this.raise(node.start, 'JSX attributes must only be assigned a non-empty expression');
        } else {
          return node;
        }

      case ttIns.jsxTagStart:
      case ttIns.string:
        node = this.parseExprAtom(null);
        node.extra = null;
        return node;

      default:
        this.raise(this.state.start, 'JSX value should be either an expression or a quoted JSX text');
    }
    return null;
  }
  jsxParseEmptyExpression(): Node {
    let node = this.startNodeAt(this.state.lastTokEnd, this.state.lastTokEndLoc);
    return this.finishNodeAt(node, 'JSXEmptyExpression', this.state.start, this.state.startLoc!);
  }
  jsxParseSpreadChild(): Node {
    let node = this.startNode();
    this.expect(ttIns.braceL, null);
    this.expect(ttIns.ellipsis, null);
    node.expression = this.parseExpression(null, null);
    this.expect(ttIns.braceR, null);

    return this.finishNode(node, 'JSXSpreadChild');
  }
  jsxParseExpressionContainer(): Node {
    let node = this.startNode();
    this.next();
    if (this.match(ttIns.braceR)) {
      node.expression = this.jsxParseEmptyExpression();
    } else {
      node.expression = this.parseExpression(null, null);
    }
    this.expect(ttIns.braceR, null);
    return this.finishNode(node, 'JSXExpressionContainer');
  }
  jsxParseAttribute(): Node {
    let node = this.startNode();
    if (this.eat(ttIns.braceL)) {
      this.expect(ttIns.ellipsis, null);
      node.argument = this.parseMaybeAssign(null, null, null, null);
      this.expect(ttIns.braceR, null);
      return this.finishNode(node, 'JSXSpreadAttribute');
    }
    node.name = this.jsxParseNamespacedName();
    node.value = this.eat(ttIns.eq) ? this.jsxParseAttributeValue() : null;
    return this.finishNode(node, 'JSXAttribute');
  }
  jsxParseOpeningElementAt(startPos: number, startLoc: Position): Node {
    let node = this.startNodeAt(startPos, startLoc);
    node.attributes = new Array();
    node.name = this.jsxParseElementName();
    while (!this.match(ttIns.slash) && !this.match(ttIns.jsxTagEnd)) {
      node.attributes.push(this.jsxParseAttribute());
    }
    node.selfClosing = this.eat(ttIns.slash);
    this.expect(ttIns.jsxTagEnd, null);
    return this.finishNode(node, 'JSXOpeningElement');
  }
  jsxParseClosingElementAt(startPos: number, startLoc: Position): Node {
    let node = this.startNodeAt(startPos, startLoc);
    node.name = this.jsxParseElementName();
    this.expect(ttIns.jsxTagEnd, null);
    return this.finishNode(node, 'JSXClosingElement');
  }
  jsxParseElementAt(startPosIn: number, startLoc: Position): Node {
    let startPos = startPosIn;
    let node = this.startNodeAt(startPos, startLoc);
    let childrens: Node[] = new Array();
    let openingElement = this.jsxParseOpeningElementAt(startPos, startLoc);
    let closingElement: Node | null = null;

    if (!openingElement.selfClosing) {
      contents: while (true) {
        switch (this.state.type) {
          case ttIns.jsxTagStart:
            startPos = this.state.start;
            startLoc = this.state.startLoc!;
            this.next();
            if (this.eat(ttIns.slash)) {
              closingElement = this.jsxParseClosingElementAt(startPos, startLoc);
              break contents;
            }
            childrens.push(this.jsxParseElementAt(startPos, startLoc));
            break;

          case ttIns.jsxText:
            childrens.push(this.parseExprAtom(null));
            break;
          case ttIns.braceL:
            if (this.lookahead().type === ttIns.ellipsis) {
              childrens.push(this.jsxParseSpreadChild());
            } else {
              childrens.push(this.jsxParseExpressionContainer());
            }

            break;
          default:
            this.unexpected(null);
        }
      }

      if (getQualifiedJSXName(closingElement.name as Node) !== getQualifiedJSXName(openingElement.name as Node)) {
        this.raise(closingElement.start, 'Expected corresponding JSX closing tag for <' + getQualifiedJSXName(openingElement.name as Node) + '>');
      }
    }

    node.openingElement = openingElement;
    node.closingElement = closingElement;
    node.children = childrens;
    if (this.match(ttIns.relational) && this.state.value === '<') {
      this.raise(this.state.start, 'Adjacent JSX elements must be wrapped in an enclosing tag');
    }
    return this.finishNode(node, 'JSXElement');
  }
  jsxParseElement(): Node {
    let startPos = this.state.start;
    let startLoc = this.state.startLoc!;
    this.next();
    return this.jsxParseElementAt(startPos, startLoc);
  }

  parseExprAtom(refShorthandDefaultPos: ExpPos | null): Node {
    if (this.match(ttIns.jsxText)) {
      let node = this.parseLiteral(this.state.value, 'JSXText', null, null);
      node.extra = null;
      return node;
    } else if (this.match(ttIns.jsxTagStart)) {
      return this.jsxParseElement();
    } else {
      return super.parseExprAtom(refShorthandDefaultPos);
    }
  }

  readToken(code: number): void {
    if (this.state.inPropertyName) {
      super.readToken(code);
    }

    let context = this.curContext();

    if (context === tc.jExpr) {
      this.jsxReadToken();
    }

    if (context === tc.joTag || context === tc.jcTag) {
      if (isIdentifierStart(code)) {
        return this.jsxReadWord();
      }

      if (code === INT_VAL_P_62) {
        this.state.pos += 1;
        return this.finishToken(ttIns.jsxTagEnd, null);
      }

      if ((code === INT_VAL_P_34 || code === INT_VAL_P_39) && context === tc.joTag) {
        return this.jsxReadString(code);
      }
    }

    if (code === INT_VAL_P_60 && this.state.exprAllowed) {
      this.state.pos += 1;
      return this.finishToken(ttIns.jsxTagStart, null);
    }

    return super.readToken(code);
  }

  updateContext(prevType: TokenType): void {
    if (this.match(ttIns.braceL)) {
      let curContext = this.curContext();
      if (curContext === tc.joTag) {
        this.state.context!.push(tc.braceExpression);
      } else if (curContext === tc.jExpr) {
        this.state.context!.push(tc.templateQuasi);
      } else {
        super.updateContext(prevType);
      }
      this.state.exprAllowed = true;
    } else if (this.match(ttIns.slash) && prevType === ttIns.jsxTagStart) {
      this.state.context!.push(tc.jcTag);
      this.state.exprAllowed = false;
    } else {
      super.updateContext(prevType);
    }
  }
}

class FlowParser extends JsxParser {
  flowParseTypeInitialiser(tok: TokenType | null): Node {
    let oldInType = this.state.inType;
    this.state.inType = true;
    this.expect(tok || ttIns.colon, null);
    let type = this.flowParseType();
    this.state.inType = oldInType;
    return type;
  }

  flowParsePredicate(): Node {
    let node = this.startNode();
    let moduloLoc = this.state.startLoc!;
    let moduloPos = this.state.start;
    this.expect(ttIns.modulo, null);
    let checksLoc = this.state.startLoc!;
    this.expectContextual('checks', null);
    if (moduloLoc.line !== checksLoc.line || moduloLoc.column !== checksLoc.column - 1) {
      this.raise(moduloPos, 'Spaces between ´%´ and ´checks´ are not allowed here.');
    }
    if (this.eat(ttIns.parenL)) {
      node.expression = this.parseExpression(null, null);
      this.expect(ttIns.parenR, null);
      return this.finishNode(node, 'DeclaredPredicate');
    } else {
      return this.finishNode(node, 'InferredPredicate');
    }
  }
  flowParseTypeAndPredicateInitialiser(): [Node | null, Node | null] {
    let oldInType = this.state.inType;
    this.state.inType = true;
    this.expect(ttIns.colon, null);
    let type: Node | null = null;
    let predicate: Node | null = null;
    if (this.match(ttIns.modulo)) {
      this.state.inType = oldInType;
      predicate = this.flowParsePredicate();
    } else {
      type = this.flowParseType();
      this.state.inType = oldInType;
      if (this.match(ttIns.modulo)) {
        predicate = this.flowParsePredicate();
      }
    }
    return [type, predicate];
  }

  flowParseDeclareClass(node: Node): Node {
    this.next();
    this.flowParseInterfaceish(node, true);
    return this.finishNode(node, 'DeclareClass');
  }

  flowParseDeclareFunction(node: Node): Node {
    this.next();

    node.id = this.parseIdentifier(null);
    let id = node.id;
    let typeNode = this.startNode();
    let typeContainer = this.startNode();

    if (this.isRelational('<')) {
      typeNode.typeParameters = this.flowParseTypeParameterDeclaration();
    } else {
      typeNode.typeParameters = null;
    }

    this.expect(ttIns.parenL, null);
    let tmp = this.flowParseFunctionTypeParams();
    typeNode.params = tmp.params;
    typeNode.rest = tmp.rest;
    this.expect(ttIns.parenR, null);
    let predicate: Node | null = null;
    typeNode.returnType = this.flowParseTypeAndPredicateInitialiser()[0];
    predicate = this.flowParseTypeAndPredicateInitialiser()[1];
    typeContainer.typeAnnotation = this.finishNode(typeNode, 'FunctionTypeAnnotation');
    typeContainer.predicate = predicate;
    id.typeAnnotation = this.finishNode(typeContainer, 'TypeAnnotation');

    this.finishNode(id, id.type);

    this.semicolon();

    return this.finishNode(node, 'DeclareFunction');
  }

  flowParseDeclare(node: Node): Node | null {
    if (this.match(ttIns._class)) {
      return this.flowParseDeclareClass(node);
    } else if (this.match(ttIns._function)) {
      return this.flowParseDeclareFunction(node);
    } else if (this.match(ttIns._var)) {
      return this.flowParseDeclareVariable(node);
    } else if (this.isContextual('module')) {
      if (this.lookahead().type === ttIns.dot) {
        return this.flowParseDeclareModuleExports(node);
      } else {
        return this.flowParseDeclareModule(node);
      }
    } else if (this.isContextual('type')) {
      return this.flowParseDeclareTypeAlias(node);
    } else if (this.isContextual('interface')) {
      return this.flowParseDeclareInterface(node);
    } else {
      return this.unexpected(null);
    }
  }

  flowParseDeclareVariable(node: Node): Node {
    this.next();
    node.id = this.flowParseTypeAnnotatableIdentifier();
    this.semicolon();
    return this.finishNode(node, 'DeclareVariable');
  }
  flowParseDeclareModule(node: Node): Node {
    this.next();

    if (this.match(ttIns.string)) {
      node.id = this.parseExprAtom(null);
    } else {
      node.id = this.parseIdentifier(null);
    }

    node.body = this.startNode();
    let bodyNode = node.body;
    bodyNode.body = new Array<Node>();
    let body: Node[] = bodyNode.body;
    this.expect(ttIns.braceL, null);
    while (!this.match(ttIns.braceR)) {
      let bodyNode = this.startNode();

      if (this.match(ttIns._import)) {
        let lookahead = this.lookahead();
        if (lookahead.value !== 'type' && lookahead.value !== 'typeof') {
          this.unexpected(null, 'Imports within a `declare module` body must always be `import type` or `import typeof`');
        }

        this.parseImport(bodyNode);
      } else {
        this.expectContextual('declare', 'Only declares and type imports are allowed inside declare module');

        bodyNode = this.flowParseDeclare(bodyNode)!;
      }

      body.push(bodyNode);
    }
    this.expect(ttIns.braceR, null);
    this.finishNode(bodyNode, 'BlockStatement');
    return this.finishNode(node, 'DeclareModule');
  }

  flowParseDeclareModuleExports(node: Node): Node {
    this.expectContextual('module', null);
    this.expect(ttIns.dot, null);
    this.expectContextual('exports', null);
    node.typeAnnotation = this.flowParseTypeAnnotation();
    this.semicolon();
    return this.finishNode(node, 'DeclareModuleExports');
  }
  flowParseDeclareTypeAlias(node: Node): Node {
    this.next();
    this.flowParseTypeAlias(node);
    return this.finishNode(node, 'DeclareTypeAlias');
  }
  flowParseDeclareInterface(node: Node): Node {
    this.next();
    this.flowParseInterfaceish(node);
    return this.finishNode(node, 'DeclareInterface');
  }
  flowParseInterfaceish(node: Node, allowStatic: boolean = false): void {
    node.id = this.parseIdentifier(null);

    if (this.isRelational('<')) {
      node.typeParameters = this.flowParseTypeParameterDeclaration();
    } else {
      node.typeParameters = null;
    }

    node.extendsArr = new Array();
    node.mixins = new Array();

    if (this.eat(ttIns._extends)) {
      do {
        node.extendsArr.push(this.flowParseInterfaceExtends());
      } while (this.eat(ttIns.comma));
    }

    if (this.isContextual('mixins')) {
      this.next();
      do {
        node.mixins.push(this.flowParseInterfaceExtends());
      } while (this.eat(ttIns.comma));
    }

    node.body = this.flowParseObjectType(allowStatic);
  }
  flowParseInterfaceExtends(): Node {
    let node = this.startNode();

    node.id = this.flowParseQualifiedTypeIdentifier(null, null, null);
    if (this.isRelational('<')) {
      node.typeParameters = this.flowParseTypeParameterInstantiation();
    } else {
      node.typeParameters = null;
    }

    return this.finishNode(node, 'InterfaceExtends');
  }
  flowParseInterface(node: Node): Node {
    this.flowParseInterfaceish(node, false);
    return this.finishNode(node, 'InterfaceDeclaration');
  }
  flowParseRestrictedIdentifier(liberal: boolean = false): Node {
    if (this.primitiveTypes.indexOf(this.state.value as string) > -1) {
      this.raise(this.state.start, `Cannot overwrite primitive type ${this.state.value}`);
    }

    return this.parseIdentifier(liberal);
  }
  flowParseTypeAlias(node: Node): Node {
    node.id = this.flowParseRestrictedIdentifier();

    if (this.isRelational('<')) {
      node.typeParameters = this.flowParseTypeParameterDeclaration();
    } else {
      node.typeParameters = null;
    }

    node.right = this.flowParseTypeInitialiser(ttIns.eq);
    this.semicolon();

    return this.finishNode(node, 'TypeAlias');
  }
  flowParseTypeParameter(): Node {
    let node = this.startNode();

    let variance = this.flowParseVariance();

    let ident = this.flowParseTypeAnnotatableIdentifier();
    node.name = ident.name;
    node.variance = variance;
    node.bound = ident.typeAnnotation;

    if (this.match(ttIns.eq)) {
      this.eat(ttIns.eq);
      node.default = this.flowParseType();
    }

    return this.finishNode(node, 'TypeParameter');
  }
  flowParseTypeParameterDeclaration(): Node {
    let oldInType = this.state.inType;
    let node = this.startNode();
    node.params = new Array();

    this.state.inType = true;

    // istanbul ignore else: this condition is already checked at all call sites
    if (this.isRelational('<') || this.match(ttIns.jsxTagStart)) {
      this.next();
    } else {
      this.unexpected(null);
    }

    do {
      node.params.push(this.flowParseTypeParameter());
      if (!this.isRelational('>')) {
        this.expect(ttIns.comma, null);
      }
    } while (!this.isRelational('>'));
    this.expectRelational('>');

    this.state.inType = oldInType;

    return this.finishNode(node, 'TypeParameterDeclaration');
  }
  flowParseTypeParameterInstantiation(): Node {
    let node = this.startNode();
    let oldInType = this.state.inType;
    node.params = new Array();

    this.state.inType = true;

    this.expectRelational('<');
    while (!this.isRelational('>')) {
      node.params.push(this.flowParseType());
      if (!this.isRelational('>')) {
        this.expect(ttIns.comma, null);
      }
    }
    this.expectRelational('>');

    this.state.inType = oldInType;

    return this.finishNode(node, 'TypeParameterInstantiation');
  }
  flowParseObjectPropertyKey(): Node {
    return this.match(ttIns.num) || this.match(ttIns.string) ? this.parseExprAtom(null) : this.parseIdentifier(true);
  }
  flowParseObjectTypeIndexer(node: Node, isStatic: boolean, variance: Node | null = null): Node {
    node.static = isStatic;

    this.expect(ttIns.bracketL, null);
    if (this.lookahead().type === ttIns.colon) {
      node.id = this.flowParseObjectPropertyKey();
      node.key = this.flowParseTypeInitialiser(null);
    } else {
      node.id = null;
      node.key = this.flowParseType();
    }
    this.expect(ttIns.bracketR, null);
    node.value = this.flowParseTypeInitialiser(null);
    node.variance = variance;

    let indexer = this.finishNode(node, 'ObjectTypeIndexer');
    this.flowObjectTypeSemicolon();

    return indexer;
  }
  flowParseObjectTypeMethodish(node: Node): Node {
    node.params = new Array();
    node.rest = null;
    node.typeParameters = null;

    if (this.isRelational('<')) {
      node.typeParameters = this.flowParseTypeParameterDeclaration();
    }

    this.expect(ttIns.parenL, null);
    while (this.match(ttIns.name)) {
      node.params.push(this.flowParseFunctionTypeParam());
      if (!this.match(ttIns.parenR)) {
        this.expect(ttIns.comma, null);
      }
    }

    if (this.eat(ttIns.ellipsis)) {
      node.rest = this.flowParseFunctionTypeParam();
    }
    this.expect(ttIns.parenR, null);
    node.returnType = this.flowParseTypeInitialiser(null);

    return this.finishNode(node, 'FunctionTypeAnnotation');
  }
  flowParseObjectTypeMethod(startPos: number, startLoc: Position | null, isStatic: boolean, key: Node): Node {
    let node = this.startNodeAt(startPos, startLoc);
    node.value = this.flowParseObjectTypeMethodish(this.startNodeAt(startPos, startLoc));
    node.static = isStatic;
    node.key = key;
    node.optional = false;
    this.flowObjectTypeSemicolon();
    return this.finishNode(node, 'ObjectTypeProperty');
  }
  flowParseObjectTypeCallProperty(node: Node, isStatic: boolean): Node {
    let valueNode = this.startNode();
    node.static = isStatic;
    node.value = this.flowParseObjectTypeMethodish(valueNode);
    this.flowObjectTypeSemicolon();
    return this.finishNode(node, 'ObjectTypeCallProperty');
  }
  flowParseObjectType(allowStatic: boolean, allowExact: boolean = false): Node {
    let oldInType = this.state.inType;
    this.state.inType = true;

    let nodeStart = this.startNode();
    let node: Node;
    let propertyKey: Node;
    let isStatic = false;

    nodeStart.callProperties = new Array();
    nodeStart.properties = new Array();
    nodeStart.indexers = new Array();

    let endDelim: TokenType;
    let exact: boolean;
    if (allowExact && this.match(ttIns.braceBarL)) {
      this.expect(ttIns.braceBarL, null);
      endDelim = ttIns.braceBarR;
      exact = true;
    } else {
      this.expect(ttIns.braceL, null);
      endDelim = ttIns.braceR;
      exact = false;
    }

    nodeStart.exact = exact;

    while (!this.match(endDelim)) {
      let optional = false;
      let startPos = this.state.start;
      let startLoc = this.state.startLoc;
      node = this.startNode();
      if (allowStatic && this.isContextual('static') && this.lookahead().type !== ttIns.colon) {
        this.next();
        isStatic = true;
      }

      let variance = this.flowParseVariance();

      if (this.match(ttIns.bracketL)) {
        nodeStart.indexers.push(this.flowParseObjectTypeIndexer(node, isStatic, variance));
      } else if (this.match(ttIns.parenL) || this.isRelational('<')) {
        if (variance) {
          this.unexpected(variance.start);
        }
        nodeStart.callProperties.push(this.flowParseObjectTypeCallProperty(node, isStatic));
      } else {
        propertyKey = this.flowParseObjectPropertyKey();
        if (this.isRelational('<') || this.match(ttIns.parenL)) {
          // This is a method property
          if (variance) {
            this.unexpected(variance.start);
          }
          nodeStart.properties.push(this.flowParseObjectTypeMethod(startPos, startLoc, isStatic, propertyKey));
        } else {
          if (this.eat(ttIns.question)) {
            optional = true;
          }
          node.key = propertyKey;
          node.value = this.flowParseTypeInitialiser(null);
          node.optional = optional;
          node.static = isStatic;
          node.variance = variance;
          this.flowObjectTypeSemicolon();
          nodeStart.properties.push(this.finishNode(node, 'ObjectTypeProperty'));
        }
      }

      isStatic = false;
    }

    this.expect(endDelim, null);

    let out = this.finishNode(nodeStart, 'ObjectTypeAnnotation');

    this.state.inType = oldInType;

    return out;
  }
  flowObjectTypeSemicolon(): void {
    if (!this.eat(ttIns.semi) && !this.eat(ttIns.comma) && !this.match(ttIns.braceR) && !this.match(ttIns.braceBarR)) {
      this.unexpected(null);
    }
  }
  flowParseQualifiedTypeIdentifier(startPos: number | null = 0, startLoc: Position | null, id: Node | null): Node {
    startPos = startPos !== 0 ? startPos : this.state.start;
    startLoc = startLoc !== null ? startLoc : this.state.startLoc;
    let node = id || this.parseIdentifier(null);

    while (this.eat(ttIns.dot)) {
      let node2 = this.startNodeAt(startPos, startLoc);
      node2.qualification = node;
      node2.id = this.parseIdentifier(null);
      node = this.finishNode(node2, 'QualifiedTypeIdentifier');
    }

    return node;
  }
  flowParseGenericType(startPos: number, startLoc: Position, id: Node): Node {
    let node = this.startNodeAt(startPos, startLoc);

    node.typeParameters = null;
    node.id = this.flowParseQualifiedTypeIdentifier(startPos, startLoc, id);

    if (this.isRelational('<')) {
      node.typeParameters = this.flowParseTypeParameterInstantiation();
    }

    return this.finishNode(node, 'GenericTypeAnnotation');
  }
  flowParseTypeofType(): Node {
    let node = this.startNode();
    this.expect(ttIns._typeof, null);
    node.argument = this.flowParsePrimaryType();
    return this.finishNode(node, 'TypeofTypeAnnotation');
  }
  flowParseTupleType(): Node {
    let node = this.startNode();
    node.types = new Array();
    this.expect(ttIns.bracketL, null);
    while (this.state.pos < this.input.length && !this.match(ttIns.bracketR)) {
      node.types.push(this.flowParseType());
      if (this.match(ttIns.bracketR)) {
        break;
      }
      this.expect(ttIns.comma, null);
    }
    this.expect(ttIns.bracketR, null);
    return this.finishNode(node, 'TupleTypeAnnotation');
  }
  flowParseFunctionTypeParam(): Node {
    let name: Node | null = null;
    let optional = false;
    let typeAnnotation: Node | null = null;
    let node = this.startNode();
    let lh = this.lookahead();
    if (lh.type === ttIns.colon || lh.type === ttIns.question) {
      name = this.parseIdentifier(null);
      if (this.eat(ttIns.question)) {
        optional = true;
      }
      typeAnnotation = this.flowParseTypeInitialiser(null);
    } else {
      typeAnnotation = this.flowParseType();
    }
    node.name = name;
    node.optional = optional;
    node.typeAnnotation = typeAnnotation;
    return this.finishNode(node, 'FunctionTypeParam');
  }
  reinterpretTypeAsFunctionTypeParam(type: Node): Node {
    let node = this.startNodeAt(type.start, type.loc.start);
    node.name = null;
    node.optional = false;
    node.typeAnnotation = type;
    return this.finishNode(node, 'FunctionTypeParam');
  }
  flowParseFunctionTypeParams(params: Node[] = new Array()): ParamsObj {
    let ret = new ParamsObj(params, null);
    while (!this.match(ttIns.parenR) && !this.match(ttIns.ellipsis)) {
      ret.params.push(this.flowParseFunctionTypeParam());
      if (!this.match(ttIns.parenR)) {
        this.expect(ttIns.comma, null);
      }
    }
    if (this.eat(ttIns.ellipsis)) {
      ret.rest = this.flowParseFunctionTypeParam();
    }
    return ret;
  }
  flowIdentToTypeAnnotation(startPos: number, startLoc: Position, node: Node, id: Node): Node {
    switch (id.name) {
      case 'any':
        return this.finishNode(node, 'AnyTypeAnnotation');

      case 'void':
        return this.finishNode(node, 'VoidTypeAnnotation');

      case 'bool':
      case 'boolean':
        return this.finishNode(node, 'BooleanTypeAnnotation');

      case 'mixed':
        return this.finishNode(node, 'MixedTypeAnnotation');

      case 'empty':
        return this.finishNode(node, 'EmptyTypeAnnotation');

      case 'number':
        return this.finishNode(node, 'NumberTypeAnnotation');

      case 'string':
        return this.finishNode(node, 'StringTypeAnnotation');

      default:
        return this.flowParseGenericType(startPos, startLoc, id);
    }
  }
  flowParsePrimaryType(): Node | null {
    let startPos = this.state.start;
    let startLoc = this.state.startLoc;
    let node = this.startNode();
    let tmp: ParamsObj;
    let type: Node | null = null;
    let isGroupedTypes = false;
    let oldNoAnonFunctionType = this.state.noAnonFunctionType;

    switch (this.state.type) {
      case ttIns.name:
        return this.flowIdentToTypeAnnotation(startPos, startLoc!, node, this.parseIdentifier(null));

      case ttIns.braceL:
        return this.flowParseObjectType(false, false);

      case ttIns.braceBarL:
        return this.flowParseObjectType(false, true);

      case ttIns.bracketL:
        return this.flowParseTupleType();

      case ttIns.relational:
        if (this.state.value === '<') {
          node.typeParameters = this.flowParseTypeParameterDeclaration();
          this.expect(ttIns.parenL, null);
          tmp = this.flowParseFunctionTypeParams();
          node.params = tmp.params;
          node.rest = tmp.rest;
          this.expect(ttIns.parenR, null);
          this.expect(ttIns.arrow, null);
          node.returnType = this.flowParseType();
          return this.finishNode(node, 'FunctionTypeAnnotation');
        }
        break;

      case ttIns.parenL:
        this.next();

        if (!this.match(ttIns.parenR) && !this.match(ttIns.ellipsis)) {
          if (this.match(ttIns.name)) {
            let token = this.lookahead().type;
            isGroupedTypes = token !== ttIns.question && token !== ttIns.colon;
          } else {
            isGroupedTypes = true;
          }
        }

        if (isGroupedTypes) {
          this.state.noAnonFunctionType = false;
          type = this.flowParseType();
          this.state.noAnonFunctionType = oldNoAnonFunctionType;

          if (this.state.noAnonFunctionType || !(this.match(ttIns.comma) || (this.match(ttIns.parenR) && this.lookahead().type === ttIns.arrow))) {
            this.expect(ttIns.parenR, null);
            return type;
          } else {
            this.eat(ttIns.comma);
          }
        }

        if (type !== null) {
          tmp = this.flowParseFunctionTypeParams([this.reinterpretTypeAsFunctionTypeParam(type)]);
        } else {
          tmp = this.flowParseFunctionTypeParams();
        }

        node.params = tmp.params;
        node.rest = tmp.rest;

        this.expect(ttIns.parenR, null);

        this.expect(ttIns.arrow, null);

        node.returnType = this.flowParseType();

        node.typeParameters = null;

        return this.finishNode(node, 'FunctionTypeAnnotation');

      case ttIns.string:
        return this.parseLiteral(this.state.value as string, 'StringLiteralTypeAnnotation', null, null);

      case ttIns._true:
      case ttIns._false:
        node.value = this.match(ttIns._true);
        this.next();
        return this.finishNode(node, 'BooleanLiteralTypeAnnotation');

      case ttIns.plusMin:
        if (this.state.value === '-') {
          this.next();
          if (!this.match(ttIns.num)) {
            this.unexpected(null, 'Unexpected token, expected number');
          }
          return this.parseLiteral(-fixStringValue(this.state.value), 'NumberLiteralTypeAnnotation', node.start, node.loc.start);
        }

        this.unexpected(null);
      case ttIns.num:
        return this.parseLiteral(this.state.value as string, 'NumberLiteralTypeAnnotation', null, null);

      case ttIns._null:
        node.value = this.match(ttIns._null);
        this.next();
        return this.finishNode(node, 'NullLiteralTypeAnnotation');

      case ttIns._this:
        node.value = this.match(ttIns._this);
        this.next();
        return this.finishNode(node, 'ThisTypeAnnotation');

      case ttIns.star:
        this.next();
        return this.finishNode(node, 'ExistsTypeAnnotation');

      default:
        if (this.state.type!.keyword === 'typeof') {
          return this.flowParseTypeofType();
        }
    }
    return this.unexpected(null);
  }
  flowParsePostfixType(): Node {
    let startPos = this.state.start;
    let startLoc = this.state.startLoc;
    let type = this.flowParsePrimaryType();
    while (!this.canInsertSemicolon() && this.match(ttIns.bracketL)) {
      let node = this.startNodeAt(startPos, startLoc);
      node.elementType = type;
      this.expect(ttIns.bracketL, null);
      this.expect(ttIns.bracketR, null);
      type = this.finishNode(node, 'ArrayTypeAnnotation');
    }
    return type as Node;
  }
  flowParsePrefixType(): Node {
    let node = this.startNode();
    if (this.eat(ttIns.question)) {
      node.typeAnnotation = this.flowParsePrefixType();
      return this.finishNode(node, 'NullableTypeAnnotation');
    } else {
      return this.flowParsePostfixType();
    }
  }
  flowParseAnonFunctionWithoutParens(): Node {
    let param = this.flowParsePrefixType();
    if (!this.state.noAnonFunctionType && this.eat(ttIns.arrow)) {
      let node = this.startNodeAt(param.start, param.loc.start);
      node.params = [this.reinterpretTypeAsFunctionTypeParam(param)];
      node.rest = null;
      node.returnType = this.flowParseType();
      node.typeParameters = null;
      return this.finishNode(node, 'FunctionTypeAnnotation');
    }
    return param;
  }
  flowParseIntersectionType(): Node {
    let node = this.startNode();
    this.eat(ttIns.bitwiseAND);
    let type = this.flowParseAnonFunctionWithoutParens();
    node.types = [type];
    while (this.eat(ttIns.bitwiseAND)) {
      node.types.push(this.flowParseAnonFunctionWithoutParens());
    }
    return node.types.length === 1 ? type : this.finishNode(node, 'IntersectionTypeAnnotation');
  }
  flowParseUnionType(): Node {
    let node = this.startNode();
    this.eat(ttIns.bitwiseOR);
    let type = this.flowParseIntersectionType();
    node.types = [type];
    while (this.eat(ttIns.bitwiseOR)) {
      node.types.push(this.flowParseIntersectionType());
    }
    return node.types.length === 1 ? type : this.finishNode(node, 'UnionTypeAnnotation');
  }
  flowParseType(): Node {
    let oldInType = this.state.inType;
    this.state.inType = true;
    let type = this.flowParseUnionType();
    this.state.inType = oldInType;
    return type;
  }
  flowParseTypeAnnotation(): Node {
    let node = this.startNode();
    node.typeAnnotation = this.flowParseTypeInitialiser(null);
    return this.finishNode(node, 'TypeAnnotation');
  }
  flowParseTypeAndPredicateAnnotation(): Node {
    let node = this.startNode();
    node.typeAnnotation = this.flowParseTypeAndPredicateInitialiser()[0];
    node.predicate = this.flowParseTypeAndPredicateInitialiser()[1];
    return this.finishNode(node, 'TypeAnnotation');
  }
  flowParseTypeAnnotatableIdentifier(): Node {
    let ident = this.flowParseRestrictedIdentifier();
    if (this.match(ttIns.colon)) {
      ident.typeAnnotation = this.flowParseTypeAnnotation();
      this.finishNode(ident, ident.type);
    }
    return ident;
  }
  typeCastToParameter(node: Node): Node {
    let nodeExp = node.expression as Node;
    nodeExp.typeAnnotation = node.typeAnnotation;

    return this.finishNodeAt(nodeExp, nodeExp.type, node.typeAnnotation!.end!, node.typeAnnotation!.loc!.end!);
  }
  flowParseVariance(): Node {
    let variance: Node | null = null;
    if (this.match(ttIns.plusMin)) {
      variance = this.startNode();
      if (this.state.value === '+') {
        variance.kind = 'plus';
      } else {
        variance.kind = 'minus';
      }
      this.next();
      this.finishNode(variance, 'Variance');
    }
    return variance as Node;
  }
  parseFunctionBody(node: Node, allowExpression: boolean | null): void {
    if (this.match(ttIns.colon) && !allowExpression) {
      node.returnType = this.flowParseTypeAndPredicateAnnotation();
    }

    return super.parseFunctionBody(node, allowExpression);
  }
  parseStatement(declaration: boolean, topLevel: boolean | null): Node {
    if (this.state.strict && this.match(ttIns.name) && this.state.value === 'interface') {
      let node = this.startNode();
      this.next();
      return this.flowParseInterface(node);
    } else {
      return super.parseStatement(declaration, topLevel);
    }
  }

  parseExpressionStatement(node: Node, expr: Node): Node | null {
    if (expr.type === 'Identifier') {
      if (expr.name === 'declare') {
        if (this.match(ttIns._class) || this.match(ttIns.name) || this.match(ttIns._function) || this.match(ttIns._var)) {
          return this.flowParseDeclare(node);
        }
      } else if (this.match(ttIns.name)) {
        if (expr.name === 'interface') {
          return this.flowParseInterface(node);
        } else if (expr.name === 'type') {
          return this.flowParseTypeAlias(node);
        }
      }
    }
    return super.parseExpressionStatement(node, expr);
  }

  shouldParseExportDeclaration(): boolean {
    return this.isContextual('type') || this.isContextual('interface') || super.shouldParseExportDeclaration();
  }
  parseConditional(expr: Node, noIn: boolean, startPos: number, startLoc: Position, refNeedsArrowPos: State): Node {
    if (refNeedsArrowPos && this.match(ttIns.question)) {
      let state = this.state.clone(null);
      try {
        return super.parseConditional(expr, noIn, startPos, startLoc, null);
      } catch (err) {
        if (err instanceof ParserError) {
          this.state = state;
          refNeedsArrowPos.start = err.pos || this.state.start;
          return expr;
        } else {
          throw err as ParserError;
        }
      }
    }

    return super.parseConditional(expr, noIn, startPos, startLoc, null);
  }
  parseParenItem(node: Node, startPos: number, startLoc: Position): Node {
    node = super.parseParenItem(node, startPos, startLoc);
    if (this.eat(ttIns.question)) {
      node.optional = true;
    }

    if (this.match(ttIns.colon)) {
      let typeCastNode = this.startNodeAt(startPos, startLoc);
      typeCastNode.expression = node;
      typeCastNode.typeAnnotation = this.flowParseTypeAnnotation();

      return this.finishNode(typeCastNode, 'TypeCastExpression');
    }

    return node;
  }
  parseExport(node: Node): Node {
    node = super.parseExport(node);
    if (node.type === 'ExportNamedDeclaration') {
      node.exportKind = node.exportKind || 'value';
    }
    return node;
  }
  parseExportDeclaration(node: Node | null): Node | null {
    if (this.isContextual('type')) {
      node!.exportKind = 'type';

      let declarationNode = this.startNode();
      this.next();

      if (this.match(ttIns.braceL)) {
        node!.specifiers = this.parseExportSpecifiers();
        this.parseExportFrom(node!, null);
        return null;
      } else {
        return this.flowParseTypeAlias(declarationNode);
      }
    } else if (this.isContextual('interface')) {
      node!.exportKind = 'type';
      let declarationNode = this.startNode();
      this.next();
      return this.flowParseInterface(declarationNode);
    } else {
      return super.parseExportDeclaration(node!);
    }
  }
  parseClassId(node: Node, isStatement: boolean, optionalId: boolean): void {
    super.parseClassId(node, isStatement, optionalId);
    if (this.isRelational('<')) {
      node.typeParameters = this.flowParseTypeParameterDeclaration();
    }
  }
  isKeyword(word: string): boolean {
    if (this.state.inType && word === 'void') {
      return false;
    } else {
      return super.isKeyword(word);
    }
  }
  readToken(code: number): void {
    if (this.state.inType && (code === INT_VAL_P_62 || code === INT_VAL_P_60)) {
      return this.finishOp(ttIns.relational, 1);
    } else {
      return super.readToken(code);
    }
  }
  jsxReadToken(): void {
    if (!this.state.inType) {
      super.jsxReadToken();
    }
  }
  toAssignable(node: Node, isBinding: boolean | null, contextDescription: string): Node {
    if (node.type === 'TypeCastExpression') {
      return super.toAssignable(this.typeCastToParameter(node), isBinding, contextDescription);
    } else {
      return super.toAssignable(node, isBinding, contextDescription);
    }
  }
  toAssignableList(exprList: Node[], isBinding: boolean, contextDescription: string): Node[] {
    let exprListCopy = exprList;
    for (let i = 0; i < exprListCopy.length; i++) {
      let expr = exprListCopy[i];
      if (expr && expr.type === 'TypeCastExpression') {
        exprListCopy[i] = this.typeCastToParameter(expr);
      }
    }
    return super.toAssignableList(exprListCopy, isBinding, contextDescription);
  }
  toReferencedList(exprList: Node[]): Node[] {
    for (let i = 0; i < exprList.length; i++) {
      let expr = exprList[i];
      if (expr && expr._exprListItem && expr.type === 'TypeCastExpression') {
        this.raise(expr.start, 'Unexpected type cast');
      }
    }
    return exprList;
  }
  parseExprListItem(allowEmpty: boolean, refShorthandDefaultPos: ExpPos, refNeedsArrowPos: ExpPos | null): Node | null {
    let container = this.startNode();
    let node = super.parseExprListItem(allowEmpty, refShorthandDefaultPos, refNeedsArrowPos);
    if (this.match(ttIns.colon)) {
      container._exprListItem = true;
      container.expression = node;
      container.typeAnnotation = this.flowParseTypeAnnotation();
      return this.finishNode(container, 'TypeCastExpression');
    } else {
      return node;
    }
  }
  checkLVal(node: Node, isBinding: boolean | null, checkClashes: Map<string, boolean> | null, contextDescription: string): void {
    if (node.type !== 'TypeCastExpression') {
      super.checkLVal(node, isBinding, checkClashes, contextDescription);
    }
  }
  parseClassProperty(node: Node): Node {
    if (this.match(ttIns.colon)) {
      node.typeAnnotation = this.flowParseTypeAnnotation();
    }
    return super.parseClassProperty(node);
  }
  isClassMethod(): boolean {
    return this.isRelational('<') || super.isClassMethod();
  }
  isClassProperty(): boolean {
    return this.match(ttIns.colon) || super.isClassProperty();
  }
  parseClassMethod(classBody: Node, method: Node, isGenerator: boolean, isAsync: boolean): void {
    if (method.variance) {
      this.unexpected(method.variance.start);
    }
    method.variance = null;
    if (this.isRelational('<')) {
      method.typeParameters = this.flowParseTypeParameterDeclaration();
    }

    super.parseClassMethod(classBody, method, isGenerator, isAsync);
  }
  parseClassSuper(node: Node, isStatement: boolean): void {
    super.parseClassSuper(node, isStatement);
    if (node.superClass && this.isRelational('<')) {
      node.superTypeParameters = this.flowParseTypeParameterInstantiation();
    }
    if (this.isContextual('implements')) {
      this.next();
      node.implements = new Array();
      let implemented: Node[] = new Array();
      do {
        let node = this.startNode();
        node.id = this.parseIdentifier(null);
        if (this.isRelational('<')) {
          node.typeParameters = this.flowParseTypeParameterInstantiation();
        } else {
          node.typeParameters = null;
        }
        implemented.push(this.finishNode(node, 'ClassImplements'));
      } while (this.eat(ttIns.comma));
    }
  }
  parsePropertyName(node: Node): Node {
    let variance = this.flowParseVariance();
    let key = super.parsePropertyName(node);
    node.variance = variance;
    return key;
  }
  parseObjPropValue(
    prop: Node,
    startPos: number,
    startLoc: Position,
    isGenerator: boolean,
    isAsync: boolean,
    isPattern: boolean,
    refShorthandDefaultPos: ExpPos
  ): Node | null {
    if (prop.variance) {
      this.unexpected(prop.variance.start);
    }
    prop.variance = null;

    let typeParameters: Node | null = null;

    if (this.isRelational('<')) {
      typeParameters = this.flowParseTypeParameterDeclaration();
      if (!this.match(ttIns.parenL)) {
        this.unexpected(null);
      }
    }

    super.parseObjPropValue(prop, startPos, startLoc, isGenerator, isAsync, isPattern, refShorthandDefaultPos);

    if (typeParameters !== null) {
      ((prop.value || prop) as Node).typeParameters = typeParameters;
    }
    return null;
  }

  parseAssignableListItemTypes(param: Node): Node {
    if (this.eat(ttIns.question)) {
      param.optional = true;
    }
    if (this.match(ttIns.colon)) {
      param.typeAnnotation = this.flowParseTypeAnnotation();
    }
    this.finishNode(param, param.type);
    return param;
  }
  parseMaybeDefault(startPos: number | null, startLoc: Position | null, left: Node | null): Node {
    let node = super.parseMaybeDefault(startPos, startLoc, left);

    if (node.type === 'AssignmentPattern' && node.typeAnnotation !== null && node.right!.start < node.typeAnnotation.start) {
      this.raise(
        node.typeAnnotation.start,
        'Type annotations must come before default assignments, e.g. instead of `age = 25: number` use `age: number = 25`'
      );
    }

    return node;
  }
  parseImportSpecifiers(node: Node): void {
    node.importKind = 'value';

    let kind: string | null = null;
    if (this.match(ttIns._typeof)) {
      kind = 'typeof';
    } else if (this.isContextual('type')) {
      kind = 'type';
    }
    if (kind) {
      let lh = this.lookahead();
      if ((lh.type === ttIns.name && lh.value !== 'from') || lh.type === ttIns.braceL || lh.type === ttIns.star) {
        this.next();
        node.importKind = kind;
      }
    }

    super.parseImportSpecifiers(node);
  }
  parseImportSpecifier(node: Node): void {
    let specifier = this.startNode();
    let firstIdentLoc = this.state.start;
    let firstIdent = this.parseIdentifier(true);

    let specifierTypeKind: string | null = null;
    if (firstIdent.name === 'type') {
      specifierTypeKind = 'type';
    } else if (firstIdent.name === 'typeof') {
      specifierTypeKind = 'typeof';
    }

    let isBinding = false;
    if (this.isContextual('as')) {
      let asIdent = this.parseIdentifier(true);
      if (specifierTypeKind !== null && !this.match(ttIns.name) && !this.state.type!.keyword) {
        specifier.imported = asIdent;
        specifier.importKind = specifierTypeKind;
        specifier.local = asIdent.clone();
      } else {
        specifier.imported = firstIdent;
        specifier.importKind = null;
        specifier.local = this.parseIdentifier(null);
      }
    } else if (specifierTypeKind !== null && (this.match(ttIns.name) || this.state.type!.keyword)) {
      specifier.imported = this.parseIdentifier(true);
      specifier.importKind = specifierTypeKind;
      if (this.eatContextual('as')) {
        specifier.local = this.parseIdentifier(null);
      } else {
        isBinding = true;
        specifier.local = specifier.imported.clone();
      }
    } else {
      isBinding = true;
      specifier.imported = firstIdent;
      specifier.importKind = null;
      specifier.local = specifier.imported.clone();
    }

    if ((node.importKind === 'type' || node.importKind === 'typeof') && (specifier.importKind === 'type' || specifier.importKind === 'typeof')) {
      this.raise(
        firstIdentLoc,
        '`The `type` and `typeof` keywords on named imports can only be used on regular `import` statements. It cannot be used with `import type` or `import typeof` statements`'
      );
    }

    if (isBinding) {
      this.checkReservedWord(specifier.local.name as string, specifier.start, true, true);
    }

    this.checkLVal(specifier.local, true, null, 'import specifier');
    node.specifiers!.push(this.finishNode(specifier, 'ImportSpecifier'));
  }
  parseFunctionParams(node: Node): void {
    if (this.isRelational('<')) {
      node.typeParameters = this.flowParseTypeParameterDeclaration();
    }
    super.parseFunctionParams(node);
  }
  parseVarHead(decl: Node): void {
    super.parseVarHead(decl);
    if (this.match(ttIns.colon)) {
      decl.id!.typeAnnotation = this.flowParseTypeAnnotation();
      this.finishNode(decl.id!, decl.id!.type);
    }
  }
  parseAsyncArrowFromCallExpression(node: Node, call: Node): Node {
    if (this.match(ttIns.colon)) {
      let oldNoAnonFunctionType = this.state.noAnonFunctionType;
      this.state.noAnonFunctionType = true;
      node.returnType = this.flowParseTypeAnnotation();
      this.state.noAnonFunctionType = oldNoAnonFunctionType;
    }

    return super.parseAsyncArrowFromCallExpression(node, call);
  }
  shouldParseAsyncArrow(): boolean {
    return this.match(ttIns.colon) || super.shouldParseAsyncArrow();
  }
  parseMaybeAssign(
    noIn: boolean | null,
    refShorthandDefaultPos: ExpPos | null,
    afterLeftParse: ((tar: Parser, n: Node, pos: number, loc: Position) => Node) | null,
    refNeedsArrowPos: ExpPos | null
  ): Node {
    let jsxError: ParserError | null = null;
    if (ttIns.jsxTagStart && this.match(ttIns.jsxTagStart)) {
      let state = this.state.clone(null);
      try {
        return super.parseMaybeAssign(noIn, refShorthandDefaultPos, afterLeftParse, refNeedsArrowPos);
      } catch (err) {
        if (err instanceof ParserError) {
          this.state = state;
          jsxError = err;
        } else {
          throw err as ParserError;
        }
      }
    }

    if (jsxError != null || this.isRelational('<')) {
      this.state.context!.push(tc.parenExpression);
      let arrowExpression: Node;
      let typeParameters: Node;
      try {
        typeParameters = this.flowParseTypeParameterDeclaration();
        arrowExpression = super.parseMaybeAssign(noIn, refShorthandDefaultPos, afterLeftParse, refNeedsArrowPos);
        arrowExpression.typeParameters = typeParameters;
        this.resetStartLocationFromNode(arrowExpression, typeParameters);
      } catch (err) {
        this.state.context!.pop();

        throw jsxError || (err as ParserError);
      }

      this.state.context!.pop();

      if (arrowExpression.type === 'ArrowFunctionExpression') {
        return arrowExpression;
      } else if (jsxError != null) {
        throw jsxError;
      } else {
        this.raise(typeParameters.start, 'Expected an arrow function after this type parameter declaration');
      }
    }

    return super.parseMaybeAssign(noIn, refShorthandDefaultPos, afterLeftParse, refNeedsArrowPos);
  }
  parseArrow(node: Node): Node | null {
    if (this.match(ttIns.colon)) {
      let state = this.state.clone(null);
      try {
        let oldNoAnonFunctionType = this.state.noAnonFunctionType;
        this.state.noAnonFunctionType = true;
        let returnType = this.flowParseTypeAndPredicateAnnotation();
        this.state.noAnonFunctionType = oldNoAnonFunctionType;

        if (this.canInsertSemicolon()) {
          this.unexpected(null);
        }
        if (!this.match(ttIns.arrow)) {
          this.unexpected(null);
        }
        node.returnType = returnType;
      } catch (err) {
        if (err instanceof SyntaxError) {
          this.state = state;
        } else {
          throw err as SyntaxError;
        }
      }
    }

    return super.parseArrow(node);
  }
  shouldParseArrow(): boolean {
    return this.match(ttIns.colon) || super.shouldParseArrow();
  }
}

export { FlowParser, Node, ParserOptions, Parser };
